/*-------------------------------------------------------------------------
 *
 * varlena.c
 *	  变量长度内置类型的函数。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/varlena.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <ctype.h>
#include <limits.h>

#include "access/detoast.h"
#include "access/toast_compression.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
#include "common/hashfn.h"
#include "common/int.h"
#include "common/unicode_norm.h"
#include "funcapi.h"
#include "lib/hyperloglog.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "nodes/execnodes.h"
#include "parser/scansup.h"
#include "port/pg_bswap.h"
#include "regex/regex.h"
#include "utils/builtins.h"
#include "utils/bytea.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/pg_locale.h"
#include "utils/sortsupport.h"
#include "utils/varlena.h"


/* GUC 变量 */
int			bytea_output = BYTEA_OUTPUT_HEX;

typedef struct varlena unknown;
typedef struct varlena VarString;

/* text_position_* 函数的状态。 */
typedef struct
{
	bool		is_multibyte_char_in_char;	/* 需要检查字符边界吗？ */

	char	   *str1;			/* haystack 字符串 */
	char	   *str2;			/* needle 字符串 */
	int			len1;			/* 字符串长度（以字节为单位） */
	int			len2;

	/* Boyer-Moore-Horspool 搜索算法的跳过表： */
	int			skiptablemask;	/* 与跳过表下标进行 AND 运算的掩码 */
	int			skiptable[256]; /* 给定不匹配字符的跳过距离 */

	char	   *last_match;		/* 指向 'str1' 中最后匹配项的指针 */

	/* 有时我们需要将匹配项的字节位置转换为
 * 字符位置。这些存储最后被转换的位置，
 * 以便在下一次调用时，可以从该点继续，而不是
 * 从头开始计数字符。 */
	char	   *refpoint;		/* 原始 haystack 字符串中的指针 */
	int			refpos;			/* 相同点的基于 0 的字符偏移量 */
} TextPositionState;

typedef struct
{
	char	   *buf1;			/* 第一个字符串，或缩写原始字符串 
								 * buf */
	char	   *buf2;			/* 第二个字符串，或缩写 strxfrm() buf */
	int			buflen1;		/* buf1 的分配长度 */
	int			buflen2;		/* buf2 的分配长度 */
	int			last_len1;		/* 最后 buf1 字符串/strxfrm() 输入的长度 */
	int			last_len2;		/* 最后 buf2 字符串/strxfrm() blob 的长度 */
	int			last_returned;	/* 最后比较结果（缓存） */
	bool		cache_blob;		/* buf2 是否包含 strxfrm() blob 等？ */
	bool		collate_c;
	Oid			typid;			/************************************************** 实际数据类型 (text/bpchar/bytea/name) */
	hyperLogLogState abbr_card; /* 简略的键基数状态 */
	hyperLogLogState full_card; /* 完整的键基数状态 */
	double		prop_card;		/* 所需的基数比例 */
	pg_locale_t locale;
} VarStringSortSupport;

/*
 * split_text() 的输出数据：我们输出到数组或表中。
 * tupstore 和 tupdesc 必须预先设置以输出到表。
 */
typedef struct
{
	ArrayBuildState *astate;
	Tuplestorestate *tupstore;
	TupleDesc	tupdesc;
} SplitTextOutputData;

/*
 * 这应该足够大，以便大多数字符串适应，但又足够小，
 * 以便我们在栈上使用它时感到舒服。
 */
#define TEXTBUFLEN		1024

#define DatumGetUnknownP(X)			((unknown *) PG_DETOAST_DATUM(X))
#define DatumGetUnknownPCopy(X)		((unknown *) PG_DETOAST_DATUM_COPY(X))
#define PG_GETARG_UNKNOWN_P(n)		DatumGetUnknownP(PG_GETARG_DATUM(n))
#define PG_GETARG_UNKNOWN_P_COPY(n) DatumGetUnknownPCopy(PG_GETARG_DATUM(n))
#define PG_RETURN_UNKNOWN_P(x)		PG_RETURN_POINTER(x)

#define DatumGetVarStringP(X)		((VarString *) PG_DETOAST_DATUM(X))
#define DatumGetVarStringPP(X)		((VarString *) PG_DETOAST_DATUM_PACKED(X))

static int	fc_varstrfastcmp_c(Datum fc_x, Datum fc_y, SortSupport fc_ssup);
static int	fc_bpcharfastcmp_c(Datum fc_x, Datum fc_y, SortSupport fc_ssup);
static int	fc_namefastcmp_c(Datum fc_x, Datum fc_y, SortSupport fc_ssup);
static int	fc_varlenafastcmp_locale(Datum fc_x, Datum fc_y, SortSupport fc_ssup);
static int	fc_namefastcmp_locale(Datum fc_x, Datum fc_y, SortSupport fc_ssup);
static int	fc_varstrfastcmp_locale(char *fc_a1p, int fc_len1, char *fc_a2p, int fc_len2, SortSupport fc_ssup);
static Datum fc_varstr_abbrev_convert(Datum fc_original, SortSupport fc_ssup);
static bool fc_varstr_abbrev_abort(int fc_memtupcount, SortSupport fc_ssup);
static int32 fc_text_length(Datum fc_str);
static text *fc_text_catenate(text *fc_t1, text *fc_t2);
static text *fc_text_substring(Datum fc_str,
							int32 fc_start,
							int32 fc_length,
							bool fc_length_not_specified);
static text *fc_text_overlay(text *fc_t1, text *fc_t2, int fc_sp, int fc_sl);
static int	fc_text_position(text *fc_t1, text *fc_t2, Oid fc_collid);
static void fc_text_position_setup(text *fc_t1, text *fc_t2, Oid fc_collid, TextPositionState *fc_state);
static bool fc_text_position_next(TextPositionState *fc_state);
static char *fc_text_position_next_internal(char *fc_start_ptr, TextPositionState *fc_state);
static char *fc_text_position_get_match_ptr(TextPositionState *fc_state);
static int	fc_text_position_get_match_pos(TextPositionState *fc_state);
static void fc_text_position_cleanup(TextPositionState *fc_state);
static void fc_check_collation_set(Oid fc_collid);
static int	fc_text_cmp(text *fc_arg1, text *fc_arg2, Oid fc_collid);
static bytea *fc_bytea_catenate(bytea *fc_t1, bytea *fc_t2);
static bytea *fc_bytea_substring(Datum fc_str,
							  int fc_S,
							  int fc_L,
							  bool fc_length_not_specified);
static bytea *fc_bytea_overlay(bytea *fc_t1, bytea *fc_t2, int fc_sp, int fc_sl);
static void fc_appendStringInfoText(StringInfo fc_str, const text *fc_t);
static bool fc_split_text(FunctionCallInfo fcinfo, SplitTextOutputData *fc_tstate);
static void fc_split_text_accum_result(SplitTextOutputData *fc_tstate,
									text *fc_field_value,
									text *fc_null_string,
									Oid fc_collation);
static text *fc_array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *fc_v,
									const char *fc_fldsep, const char *fc_null_string);
static StringInfo fc_makeStringAggState(FunctionCallInfo fcinfo);
static bool fc_text_format_parse_digits(const char **fc_ptr, const char *fc_end_ptr,
									 int *fc_value);
static const char *fc_text_format_parse_format(const char *fc_start_ptr,
											const char *fc_end_ptr,
											int *fc_argpos, int *fc_widthpos,
											int *fc_flags, int *fc_width);
static void fc_text_format_string_conversion(StringInfo fc_buf, char fc_conversion,
										  FmgrInfo *fc_typOutputInfo,
										  Datum fc_value, bool fc_isNull,
										  int fc_flags, int fc_width);
static void fc_text_format_append_string(StringInfo fc_buf, const char *fc_str,
									  int fc_flags, int fc_width);


/*****************************************************************************
 *	 为 C 代码导出的转换例程							 *
 *****************************************************************************/

/*
 * cstring_to_text
 *
 * 从以 null 结尾的 C 字符串创建文本值。
 *
 * 新的文本值是用完整大小的 VARHDR 新分配的。
 */
text * cstring_to_text(const char *fc_s)
{
	return cstring_to_text_with_len(fc_s, strlen(fc_s));
}

/*
 * cstring_to_text_with_len
 *
 * 与 cstring_to_text 相同，除了调用者指定字符串长度；
 * 字符串不需要以 null 结尾。
 */
text * cstring_to_text_with_len(const char *fc_s, int fc_len)
{
	text	   *fc_result = (text *) palloc(fc_len + VARHDRSZ);

	SET_VARSIZE(fc_result, fc_len + VARHDRSZ);
	memcpy(VARDATA(fc_result), fc_s, fc_len);

	return fc_result;
}

/*
 * text_to_cstring
 *
 * 从文本值创建一个以 palloc' 的、以 null 结尾的 C 字符串。
 *
 * 我们支持传入压缩或烘烤的文本值。
 * 这有点不太妥，因为这种值不应该真正被称为
 * “text *”，但对于稳健性来说，这似乎很有用。如果我们不在这里处理该
 * 情况，我们还需要另外一个例程来处理它。
 */
char * text_to_cstring(const text *fc_t)
{
	/* 不幸的是必须将 const 强制转换掉 */
	text	   *fc_tunpacked = pg_detoast_datum_packed(unconstify(text *, fc_t));
	int			fc_len = VARSIZE_ANY_EXHDR(fc_tunpacked);
	char	   *fc_result;

	fc_result = (char *) palloc(fc_len + 1);
	memcpy(fc_result, VARDATA_ANY(fc_tunpacked), fc_len);
	fc_result[fc_len] = '\0';

	if (fc_tunpacked != fc_t)
		pfree(fc_tunpacked);

	return fc_result;
}

/*
 * text_to_cstring_buffer
 *
 * 将文本值复制到调用者提供的大小为 dst_len 的缓冲区中。
 *
 * 如果需要，文本字符串会被截断以适应。结果保证以 null 结尾（除非 dst_len == 0）。
 *
 * 我们支持传入压缩或烘烤的文本值。
 * 这有点不太妥，因为这种值不应该真正被称为
 * “text *”，但对于稳健性来说，这似乎很有用。如果我们不在这里处理该
 * 情况，我们还需要另外一个例程来处理它。
 */
void text_to_cstring_buffer(const text *fc_src, char *fc_dst, size_t fc_dst_len)
{
	/* 不幸的是必须将 const 强制转换掉 */
	text	   *fc_srcunpacked = pg_detoast_datum_packed(unconstify(text *, fc_src));
	size_t		fc_src_len = VARSIZE_ANY_EXHDR(fc_srcunpacked);

	if (fc_dst_len > 0)
	{
		fc_dst_len--;
		if (fc_dst_len >= fc_src_len)
			fc_dst_len = fc_src_len;
		else					/* 确保截断是编码安全的 */
			fc_dst_len = pg_mbcliplen(VARDATA_ANY(fc_srcunpacked), fc_src_len, fc_dst_len);
		memcpy(fc_dst, VARDATA_ANY(fc_srcunpacked), fc_dst_len);
		fc_dst[fc_dst_len] = '\0';
	}

	if (fc_srcunpacked != fc_src)
		pfree(fc_srcunpacked);
}


/*****************************************************************************
 *	 用户 I/O 例程														 *
 *****************************************************************************/


#define VAL(CH)			((CH) - '0')
#define DIG(VAL)		((VAL) + '0')

/*
 *		byteain			- 从可打印的字节数组表示中转换
 *
 *		不可打印字符必须以 '\nnn'（八进制）形式传递，并被转换为内部形式。
 *		'\' 必须以 '\\' 形式传递。
 *		如果格式不良则 ereport(ERROR, ...)。
 *
 *		缺陷：
 *				输入被扫描了两次。
 *				对输入的错误检查 minimal。
 */
Datum byteain(PG_FUNCTION_ARGS)
{
	char	   *fc_inputText = PG_GETARG_CSTRING(0);
	char	   *fc_tp;
	char	   *fc_rp;
	int			fc_bc;
	bytea	   *fc_result;

	/* 识别十六进制输入 */
	if (fc_inputText[0] == '\\' && fc_inputText[1] == 'x')
	{
		size_t		fc_len = strlen(fc_inputText);

		fc_bc = (fc_len - 2) / 2 + VARHDRSZ;	/* 可能的最大长度 */
		fc_result = palloc(fc_bc);
		fc_bc = hex_decode(fc_inputText + 2, fc_len - 2, VARDATA(fc_result));
		SET_VARSIZE(fc_result, fc_bc + VARHDRSZ); /* 实际长度 */

		PG_RETURN_BYTEA_P(fc_result);
	}

	/* 否则，它是传统转义样式 */
	for (fc_bc = 0, fc_tp = fc_inputText; *fc_tp != '\0'; fc_bc++)
	{
		if (fc_tp[0] != '\\')
			fc_tp++;
		else if ((fc_tp[0] == '\\') &&
				 (fc_tp[1] >= '0' && fc_tp[1] <= '3') &&
				 (fc_tp[2] >= '0' && fc_tp[2] <= '7') &&
				 (fc_tp[3] >= '0' && fc_tp[3] <= '7'))
			fc_tp += 4;
		else if ((fc_tp[0] == '\\') &&
				 (fc_tp[1] == '\\'))
			fc_tp += 2;
		else
		{
			/*
			 * 一个反斜杠，后面不跟另一个或 ### 有效的八进制
			 */
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
					 errmsg("invalid input syntax for type %s", "bytea")));
		}
	}

	fc_bc += VARHDRSZ;

	fc_result = (bytea *) palloc(fc_bc);
	SET_VARSIZE(fc_result, fc_bc);

	fc_tp = fc_inputText;
	fc_rp = VARDATA(fc_result);
	while (*fc_tp != '\0')
	{
		if (fc_tp[0] != '\\')
			*fc_rp++ = *fc_tp++;
		else if ((fc_tp[0] == '\\') &&
				 (fc_tp[1] >= '0' && fc_tp[1] <= '3') &&
				 (fc_tp[2] >= '0' && fc_tp[2] <= '7') &&
				 (fc_tp[3] >= '0' && fc_tp[3] <= '7'))
		{
			fc_bc = VAL(fc_tp[1]);
			fc_bc <<= 3;
			fc_bc += VAL(fc_tp[2]);
			fc_bc <<= 3;
			*fc_rp++ = fc_bc + VAL(fc_tp[3]);

			fc_tp += 4;
		}
		else if ((fc_tp[0] == '\\') &&
				 (fc_tp[1] == '\\'))
		{
			*fc_rp++ = '\\';
			fc_tp += 2;
		}
		else
		{
			/*
			 * 我们永远不应该到这里。第一次遍历不应该允许它。
			 */
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
					 errmsg("invalid input syntax for type %s", "bytea")));
		}
	}

	PG_RETURN_BYTEA_P(fc_result);
}

/*
 *		byteaout		- 转换为字节数组的可打印表示
 *
 *		在传统的转义格式中，不可打印字符被打印为 '\nnn'（八进制），
 *		'&#92;' 被打印为 '\\'。
 */
Datum byteaout(PG_FUNCTION_ARGS)
{
	bytea	   *fc_vlena = PG_GETARG_BYTEA_PP(0);
	char	   *fc_result;
	char	   *fc_rp;

	if (bytea_output == BYTEA_OUTPUT_HEX)
	{
		/* 打印十六进制格式 */
		fc_rp = fc_result = palloc(VARSIZE_ANY_EXHDR(fc_vlena) * 2 + 2 + 1);
		*fc_rp++ = '\\';
		*fc_rp++ = 'x';
		fc_rp += hex_encode(VARDATA_ANY(fc_vlena), VARSIZE_ANY_EXHDR(fc_vlena), fc_rp);
	}
	else if (bytea_output == BYTEA_OUTPUT_ESCAPE)
	{
		/* 打印传统转义格式 */
		char	   *fc_vp;
		uint64		fc_len;
		int			fc_i;

		fc_len = 1;				/* 空字符串有 1 个字符 */
		fc_vp = VARDATA_ANY(fc_vlena);
		for (fc_i = VARSIZE_ANY_EXHDR(fc_vlena); fc_i != 0; fc_i--, fc_vp++)
		{
			if (*fc_vp == '\\')
				fc_len += 2;
			else if ((unsigned char) *fc_vp < 0x20 || (unsigned char) *fc_vp > 0x7e)
				fc_len += 4;
			else
				fc_len++;
		}

		/*
		 * 原则上 len 不能溢出 uint32 如果输入适合 1GB，但
		 * 为了安全起见，让我们检查一下，而不是依赖 palloc 的内部
		 * 检查。
		 */
		if (fc_len > MaxAllocSize)
			ereport(ERROR,
					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
					 errmsg_internal("result of bytea output conversion is too large")));
		fc_rp = fc_result = (char *) palloc(fc_len);

		fc_vp = VARDATA_ANY(fc_vlena);
		for (fc_i = VARSIZE_ANY_EXHDR(fc_vlena); fc_i != 0; fc_i--, fc_vp++)
		{
			if (*fc_vp == '\\')
			{
				*fc_rp++ = '\\';
				*fc_rp++ = '\\';
			}
			else if ((unsigned char) *fc_vp < 0x20 || (unsigned char) *fc_vp > 0x7e)
			{
				int			fc_val;	/* 保存不可打印字符 */

				fc_val = *fc_vp;
				fc_rp[0] = '\\';
				fc_rp[3] = DIG(fc_val & 07);
				fc_val >>= 3;
				fc_rp[2] = DIG(fc_val & 07);
				fc_val >>= 3;
				fc_rp[1] = DIG(fc_val & 03);
				fc_rp += 4;
			}
			else
				*fc_rp++ = *fc_vp;
		}
	}
	else
	{
		elog(ERROR, "unrecognized bytea_output setting: %d",
			 bytea_output);
		fc_rp = fc_result = NULL;		/* 保持编译器安静 */
#ifdef FDD//cppcheck
		 return (Datum) 0; //不能使用 PG_RETURN_NULL();
#endif
	}
	*fc_rp = '\0';
	PG_RETURN_CSTRING(fc_result);
}

/*
 *		bytearecv			- 将外部二进制格式转换为 bytea
 */
Datum bytearecv(PG_FUNCTION_ARGS)
{
	StringInfo	fc_buf = (StringInfo) PG_GETARG_POINTER(0);
	bytea	   *fc_result;
	int			fc_nbytes;

	fc_nbytes = fc_buf->len - fc_buf->cursor;
	fc_result = (bytea *) palloc(fc_nbytes + VARHDRSZ);
	SET_VARSIZE(fc_result, fc_nbytes + VARHDRSZ);
	pq_copymsgbytes(fc_buf, VARDATA(fc_result), fc_nbytes);
	PG_RETURN_BYTEA_P(fc_result);
}

/*
 *		byteasend			- 将 bytea 转换为二进制格式
 *
 * 这是一个特殊情况：只需复制输入...
 */
Datum byteasend(PG_FUNCTION_ARGS)
{
	bytea	   *fc_vlena = PG_GETARG_BYTEA_P_COPY(0);

	PG_RETURN_BYTEA_P(fc_vlena);
}

Datum bytea_string_agg_transfn(PG_FUNCTION_ARGS)
{
	StringInfo	fc_state;

	fc_state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);

	/* 除非为 null，否则附加该值。 */
	if (!PG_ARGISNULL(1))
	{
		bytea	   *fc_value = PG_GETARG_BYTEA_PP(1);

		/* 在第一次遍历时，我们忽略分隔符。 */
		if (fc_state == NULL)
			fc_state = fc_makeStringAggState(fcinfo);
		else if (!PG_ARGISNULL(2))
		{
			bytea	   *fc_delim = PG_GETARG_BYTEA_PP(2);

			appendBinaryStringInfo(fc_state, VARDATA_ANY(fc_delim), VARSIZE_ANY_EXHDR(fc_delim));
		}

		appendBinaryStringInfo(fc_state, VARDATA_ANY(fc_value), VARSIZE_ANY_EXHDR(fc_value));
	}

	/*
	 * string_agg()的转换类型被声明为"internal"，
	 * 这是一个与指针大小相同的按值传递类型。
	 */
	PG_RETURN_POINTER(fc_state);
}

Datum bytea_string_agg_finalfn(PG_FUNCTION_ARGS)
{
	StringInfo	fc_state;

	/* 由于内部类型参数，无法直接调用 */
	Assert(AggCheckCallContext(fcinfo, NULL));

	fc_state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);

	if (fc_state != NULL)
	{
		bytea	   *fc_result;

		fc_result = (bytea *) palloc(fc_state->len + VARHDRSZ);
		SET_VARSIZE(fc_result, fc_state->len + VARHDRSZ);
		memcpy(VARDATA(fc_result), fc_state->data, fc_state->len);
		PG_RETURN_BYTEA_P(fc_result);
	}
	else
		PG_RETURN_NULL();
}

/*
 *		textin			- 将"..."转换为内部表示
 */
Datum textin(PG_FUNCTION_ARGS)
{
	char	   *fc_inputText = PG_GETARG_CSTRING(0);

	PG_RETURN_TEXT_P(cstring_to_text(fc_inputText));
}

/*
 *		textout			- 将内部表示转换为"..."
 */
Datum textout(PG_FUNCTION_ARGS)
{
	Datum		fc_txt = PG_GETARG_DATUM(0);

	PG_RETURN_CSTRING(TextDatumGetCString(fc_txt));
}

/*
 *		textrecv			- 将外部二进制格式转换为文本
 */
Datum textrecv(PG_FUNCTION_ARGS)
{
	StringInfo	fc_buf = (StringInfo) PG_GETARG_POINTER(0);
	text	   *fc_result;
	char	   *fc_str;
	int			fc_nbytes;

	fc_str = pq_getmsgtext(fc_buf, fc_buf->len - fc_buf->cursor, &fc_nbytes);

	fc_result = cstring_to_text_with_len(fc_str, fc_nbytes);
	pfree(fc_str);
	PG_RETURN_TEXT_P(fc_result);
}

/*
 *		textsend			- 将文本转换为二进制格式
 */
Datum textsend(PG_FUNCTION_ARGS)
{
	text	   *fc_t = PG_GETARG_TEXT_PP(0);
	StringInfoData fc_buf;

	pq_begintypsend(&fc_buf);
	pq_sendtext(&fc_buf, VARDATA_ANY(fc_t), VARSIZE_ANY_EXHDR(fc_t));
	PG_RETURN_BYTEA_P(pq_endtypsend(&fc_buf));
}


/*
 *		unknownin			- 将"..."转换为内部表示
 */
Datum unknownin(PG_FUNCTION_ARGS)
{
	char	   *fc_str = PG_GETARG_CSTRING(0);

	/* 表示与cstring相同 */
	PG_RETURN_CSTRING(pstrdup(fc_str));
}

/*
 *		unknownout			- 将内部表示转换为"..."
 */
Datum unknownout(PG_FUNCTION_ARGS)
{
	/* 表示与cstring相同 */
	char	   *fc_str = PG_GETARG_CSTRING(0);

	PG_RETURN_CSTRING(pstrdup(fc_str));
}

/*
 *		unknownrecv			- 将外部二进制格式转换为未知
 */
Datum unknownrecv(PG_FUNCTION_ARGS)
{
	StringInfo	fc_buf = (StringInfo) PG_GETARG_POINTER(0);
	char	   *fc_str;
	int			fc_nbytes;

	fc_str = pq_getmsgtext(fc_buf, fc_buf->len - fc_buf->cursor, &fc_nbytes);
	/* 表示与cstring相同 */
	PG_RETURN_CSTRING(fc_str);
}

/*
 *		unknownsend			- 将未知转换为二进制格式
 */
Datum unknownsend(PG_FUNCTION_ARGS)
{
	/* 表示与cstring相同 */
	char	   *fc_str = PG_GETARG_CSTRING(0);
	StringInfoData fc_buf;

	pq_begintypsend(&fc_buf);
	pq_sendtext(&fc_buf, fc_str, strlen(fc_str));
	PG_RETURN_BYTEA_P(pq_endtypsend(&fc_buf));
}


/* ========== 公共例程 ========== */

/*
 * textlen -
 *	  返回文本的逻辑长度*
 *	   （小于文本的VARSIZE*）
 */
Datum textlen(PG_FUNCTION_ARGS)
{
	Datum		fc_str = PG_GETARG_DATUM(0);

	/* 尝试避免解压参数 */
	PG_RETURN_INT32(fc_text_length(fc_str));
}

/*
 * text_length -
 *	为textlen()执行实际工作
 *
 *	这被分出来，以便可以被其他字符串处理函数直接调用。注意，参数作为Datum传递，表明它可能仍处于压缩状态。我们可以在某些情况下避免完全解压。
 */
static int32 fc_text_length(Datum fc_str)
{
	/* 当最大编码长度为1时的快速路径 */
	if (pg_database_encoding_max_length() == 1)
		PG_RETURN_INT32(toast_raw_datum_size(fc_str) - VARHDRSZ);
	else
	{
		text	   *fc_t = DatumGetTextPP(fc_str);

		PG_RETURN_INT32(pg_mbstrlen_with_len(VARDATA_ANY(fc_t),
											 VARSIZE_ANY_EXHDR(fc_t)));
	}
}

/*
 * textoctetlen -
 *	  返回文本的物理长度*
 *	   （小于文本的VARSIZE*）
 */
Datum textoctetlen(PG_FUNCTION_ARGS)
{
	Datum		fc_str = PG_GETARG_DATUM(0);

	/* 我们根本无需解压输入 */
	PG_RETURN_INT32(toast_raw_datum_size(fc_str) - VARHDRSZ);
}

/*
 * textcat -
 *	  接受两个文本*并返回一个文本*，是两个文本的连接。
 *
 * 由Sapa重写，sapa@hq.icb.chel.su。96年7月8日。
 * 由Thomas更新，Thomas.Lockhart@jpl.nasa.gov 1997年7月10日。
 * 在所有情况下分配输出空间。
 * XXX - thomas 1997-07-10
 */
Datum textcat(PG_FUNCTION_ARGS)
{
	text	   *fc_t1 = PG_GETARG_TEXT_PP(0);
	text	   *fc_t2 = PG_GETARG_TEXT_PP(1);

	PG_RETURN_TEXT_P(fc_text_catenate(fc_t1, fc_t2));
}

/*
 * text_catenate
 *	textcat()的核心部分，被分出来以便其他函数使用
 *
 * 参数可以采用短头形式，但不能压缩或超出行
 */
static text * fc_text_catenate(text *fc_t1, text *fc_t2)
{
	text	   *fc_result;
	int			fc_len1,
				fc_len2,
				fc_len;
	char	   *fc_ptr;

	fc_len1 = VARSIZE_ANY_EXHDR(fc_t1);
	fc_len2 = VARSIZE_ANY_EXHDR(fc_t2);

	/* 偏执...可能应该抛出错误？ */
	if (fc_len1 < 0)
		fc_len1 = 0;
	if (fc_len2 < 0)
		fc_len2 = 0;

	fc_len = fc_len1 + fc_len2 + VARHDRSZ;
	fc_result = (text *) palloc(fc_len);

	/* 设置结果字符串的大小... */
	SET_VARSIZE(fc_result, fc_len);

	/* 填充结果字符串的数据字段... */
	fc_ptr = VARDATA(fc_result);
	if (fc_len1 > 0)
		memcpy(fc_ptr, VARDATA_ANY(fc_t1), fc_len1);
	if (fc_len2 > 0)
		memcpy(fc_ptr + fc_len1, VARDATA_ANY(fc_t2), fc_len2);

	return fc_result;
}

/*
 * charlen_to_bytelen()
 *	计算从*p开始的n个字符所占用的字节数
 *
 * 确保实际存在n个字符是调用者的责任；
 * 字符串不必以空字符结束。
 */
static int fc_charlen_to_bytelen(const char *fc_p, int fc_n)
{
	if (pg_database_encoding_max_length() == 1)
	{
		/* 针对单字节编码的优化 */
		return fc_n;
	}
	else
	{
		const char *fc_s;

		for (fc_s = fc_p; fc_n > 0; fc_n--)
			fc_s += pg_mblen(fc_s);

		return fc_s - fc_p;
	}
}

/*
 * text_substr()
 * 返回从指定位置开始的子字符串。
 * - thomas 1997-12-31
 *
 * 输入：
 *	- 字符串
 *	- 起始位置（从一开始）
 *	- 字符串长度
 *
 * 如果起始位置为零或更小，则从字符串开始处返回，
 * 调整长度以与SQL的"负起始"一致。
 * 如果长度小于零，则返回剩余字符串。
 *
 * 添加了多字节支持。
 * - Tatsuo Ishii 1998年4月21日
 * 在起始位置小于1的情况下更改行为以符合SQL行为。
 * 以前返回整个字符串；现在返回一部分。
 * - Thomas Lockhart 1998年12月10日
 * 现在使用更快的TOAST切片接口
 * - John Gray 2002年2月22日
 * 移除"#ifdef MULTIBYTE"并改为测试encoding_max_length。改变
 * 与SQL冲突的行为以符合SQL（如果E = S + L < S抛出
 * 错误；如果E <1，返回''，而不是整个字符串）。修复了MB相关的错误，当
 * S > LC且< LC + 4时，有时返回垃圾字符。
 * - Joe Conway 2002年8月10日
 */
Datum text_substr(PG_FUNCTION_ARGS)
{
	PG_RETURN_TEXT_P(fc_text_substring(PG_GETARG_DATUM(0),
									PG_GETARG_INT32(1),
									PG_GETARG_INT32(2),
									false));
}

/*
 * text_substr_no_len -
 *	  包装器，以避免由于
 *	  一个函数接受不同数量的参数而导致的opr_sanity失败。
 */
Datum text_substr_no_len(PG_FUNCTION_ARGS)
{
	PG_RETURN_TEXT_P(fc_text_substring(PG_GETARG_DATUM(0),
									PG_GETARG_INT32(1),
									-1, true));
}

/*
 * text_substring -
 *	为text_substr()和text_substr_no_len()执行实际工作
 *
 *	这被分出来，以便可以被其他字符串处理函数直接调用。注意，参数作为Datum传递，表明它可能仍处于压缩/烤制状态。我们可以在某些情况下避免完全解压。
 *
 *	结果总是一个新分配的datum。
 */
static text * fc_text_substring(Datum fc_str, int32 fc_start, int32 fc_length, bool fc_length_not_specified)
{
	int32		fc_eml = pg_database_encoding_max_length();
	int32		fc_S = fc_start;		/* 开始位置 */
	int32		fc_S1;				/* 调整后的起始位置 */
	int32		fc_L1;				/* 调整后的子字符串长度 */
	int32		fc_E;				/* 结束位置 */

	/*
	 * SQL99规定S可以为零或负数，但我们仍然必须从字符串的
	 * 开始处获取。
	 */
	fc_S1 = Max(fc_S, 1);

	/* 如果编码最大长度为1，生活就简单了 */
	if (fc_eml == 1)
	{
		if (fc_length_not_specified)	/* 特殊情况 - 获取到字符串末尾的长度 */
			fc_L1 = -1;
		else if (fc_length < 0)
		{
			/* SQL99 规定对于 E < S，即负长度抛出错误 */
			ereport(ERROR,
					(errcode(ERRCODE_SUBSTRING_ERROR),
					 errmsg("negative substring length not allowed")));
			fc_L1 = -1;			/* 消除愚蠢编译器的警告 */
		}
		else if (pg_add_s32_overflow(fc_S, fc_length, &fc_E))
		{
			/*
			 * L可能足够大，以至于S + L溢出，在这种情况下
			 * 子字符串必须延伸到字符串末尾。
			 */
			fc_L1 = -1;
		}
		else
		{
			/*
			 * 结束位置的值为零或负值，如果开始为负值或一。
			 * SQL99规定返回一个零长度字符串。
			 */
			if (fc_E < 1)
				return cstring_to_text("");

			fc_L1 = fc_E - fc_S1;
		}

		/*
		 * 如果起始位置超出了字符串的结尾，SQL99规定
		 * 返回一个零长度字符串 - DatumGetTextPSlice()会为我们完成这个
		 * 我们只需将S1转换为零基的起始位置。
		 */
		return DatumGetTextPSlice(fc_str, fc_S1 - 1, fc_L1);
	}
	else if (fc_eml > 1)
	{
		/*
		 * 当编码的最大长度大于1时，我们无法在
		 * 不去解压的情况下获取LC，所以现在我们将拿到一个保守
		 * 的大切片，稍后再回来做正确的处理
		 */
		int32		fc_slice_start;
		int32		fc_slice_size;
		int32		fc_slice_strlen;
		text	   *fc_slice;
		int32		fc_E1;
		int32		fc_i;
		char	   *fc_p;
		char	   *fc_s;
		text	   *fc_ret;

		/*
		 * 我们需要从零位置开始，因为没有办法提前知道
		 * 提供的起始位置对应的字节偏移量。
		 */
		fc_slice_start = 0;

		if (fc_length_not_specified)	/* 特殊情况 - 获取到字符串末尾的长度 */
			fc_slice_size = fc_L1 = -1;
		else if (fc_length < 0)
		{
			/* SQL99 规定对于 E < S，即负长度抛出错误 */
			ereport(ERROR,
					(errcode(ERRCODE_SUBSTRING_ERROR),
					 errmsg("negative substring length not allowed")));
			fc_slice_size = fc_L1 = -1;	/* 消除愚蠢编译器的警告 */
		}
		else if (pg_add_s32_overflow(fc_S, fc_length, &fc_E))
		{
			/*
			 * L可能足够大，以至于S + L溢出，在这种情况下
			 * 子字符串必须延伸到字符串末尾。
			 */
			fc_slice_size = fc_L1 = -1;
		}
		else
		{
			/*
			 * 结束位置的值为零或负值，如果开始为负值或一。
			 * SQL99规定返回一个零长度字符串。
			 */
			if (fc_E < 1)
				return cstring_to_text("");

			/*
			 * 如果E超出了字符串的结尾，元组制作者将
			 * 为我们截短长度
			 */
			fc_L1 = fc_E - fc_S1;

			/*
			 * 总切片大小（以字节为单位）不能超过起始
			 * 位置加上子字符串长度乘以编码的最大长度。
			 * 如果那溢出，我们可以直接使用-1。
			 */
			if (pg_mul_s32_overflow(fc_E, fc_eml, &fc_slice_size))
				fc_slice_size = -1;
		}

		/*
		 * 如果我们处理的是未解压的源，则无需进行额外
		 * 的复制步骤。
		 */
		if (VARATT_IS_COMPRESSED(DatumGetPointer(fc_str)) ||
			VARATT_IS_EXTERNAL(DatumGetPointer(fc_str)))
			fc_slice = DatumGetTextPSlice(fc_str, fc_slice_start, fc_slice_size);
		else
			fc_slice = (text *) DatumGetPointer(fc_str);

		/* 查看我们是否返回了一个空字符串 */
		if (VARSIZE_ANY_EXHDR(fc_slice) == 0)
		{
			if (fc_slice != (text *) DatumGetPointer(fc_str))
				pfree(fc_slice);
			return cstring_to_text("");
		}

		/* 现在我们可以得到切片的实际长度（以MB字符为单位） */
		fc_slice_strlen = pg_mbstrlen_with_len(VARDATA_ANY(fc_slice),
											VARSIZE_ANY_EXHDR(fc_slice));

		/*
		 * 检查开始位置是否大于切片的字符串长度。如果是，SQL99
		 * 规定返回一个零长度字符串。
		 */
		if (fc_S1 > fc_slice_strlen)
		{
			if (fc_slice != (text *) DatumGetPointer(fc_str))
				pfree(fc_slice);
			return cstring_to_text("");
		}

		/*
		 * 现在调整L1和E1，因为我们知道了切片字符串的长度。再次
		 * 记住S1是基于1的，而slice_start是基于0的。
		 */
		if (fc_L1 > -1)
			fc_E1 = Min(fc_S1 + fc_L1, fc_slice_start + 1 + fc_slice_strlen);
		else
			fc_E1 = fc_slice_start + 1 + fc_slice_strlen;

		/*
		 * 在切片中找到起始位置；记住S1不是基于0的
		 */
		fc_p = VARDATA_ANY(fc_slice);
		for (fc_i = 0; fc_i < fc_S1 - 1; fc_i++)
			fc_p += pg_mblen(fc_p);

		/* 保持对我们起始位置的指针 */
		fc_s = fc_p;

		/*
		 * 计算请求长度的子字符串实际使用的字节。
		 */
		for (fc_i = fc_S1; fc_i < fc_E1; fc_i++)
			fc_p += pg_mblen(fc_p);

		fc_ret = (text *) palloc(VARHDRSZ + (fc_p - fc_s));
		SET_VARSIZE(fc_ret, VARHDRSZ + (fc_p - fc_s));
		memcpy(VARDATA(fc_ret), fc_s, (fc_p - fc_s));

		if (fc_slice != (text *) DatumGetPointer(fc_str))
			pfree(fc_slice);

		return fc_ret;
	}
	else
		elog(ERROR, "invalid backend encoding: encoding max length < 1");

	/* 未到达：抑制编译器警告 */
	return NULL;
}

/*
 * textoverlay
 *	用第二个字符串替换第一个字符串的指定子字符串
 *
 * SQL标准通过子字符串和连接定义OVERLAY()。
 * 这段代码是对标准所言的直接实现。
 */
Datum textoverlay(PG_FUNCTION_ARGS)
{
	text	   *fc_t1 = PG_GETARG_TEXT_PP(0);
	text	   *fc_t2 = PG_GETARG_TEXT_PP(1);
	int			fc_sp = PG_GETARG_INT32(2);	/* 子字符串起始位置 */
	int			fc_sl = PG_GETARG_INT32(3);	/* 子字符串长度 */

	PG_RETURN_TEXT_P(fc_text_overlay(fc_t1, fc_t2, fc_sp, fc_sl));
}

Datum textoverlay_no_len(PG_FUNCTION_ARGS)
{
	text	   *fc_t1 = PG_GETARG_TEXT_PP(0);
	text	   *fc_t2 = PG_GETARG_TEXT_PP(1);
	int			fc_sp = PG_GETARG_INT32(2);	/* 子字符串起始位置 */
	int			fc_sl;

	fc_sl = fc_text_length(PointerGetDatum(fc_t2));	/* 默认为长度(t2) */
	PG_RETURN_TEXT_P(fc_text_overlay(fc_t1, fc_t2, fc_sp, fc_sl));
}

static text * fc_text_overlay(text *fc_t1, text *fc_t2, int fc_sp, int fc_sl)
{
	text	   *fc_result;
	text	   *fc_s1;
	text	   *fc_s2;
	int			fc_sp_pl_sl;

	/*
	 * 检查可能的整数溢出情况。对于负的 sp，抛出一个
	 * “子字符串长度”错误，因为这是根据规范对 OVERLAY() 的定义应当预期的。
	 */
	if (fc_sp <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_SUBSTRING_ERROR),
				 errmsg("negative substring length not allowed")));
	if (pg_add_s32_overflow(fc_sp, fc_sl, &fc_sp_pl_sl))
		ereport(ERROR,
				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
				 errmsg("integer out of range")));

	fc_s1 = fc_text_substring(PointerGetDatum(fc_t1), 1, fc_sp - 1, false);
	fc_s2 = fc_text_substring(PointerGetDatum(fc_t1), fc_sp_pl_sl, -1, true);
	fc_result = fc_text_catenate(fc_s1, fc_t2);
	fc_result = fc_text_catenate(fc_result, fc_s2);

	return fc_result;
}

/*
 * textpos -
 *	  返回指定子字符串的位置。
 *	  实现SQL POSITION()函数。
 *	  参考：SQL标准指南，Date & Darwen，1997
 * - thomas 1997-07-27
 */
Datum textpos(PG_FUNCTION_ARGS)
{
	text	   *fc_str = PG_GETARG_TEXT_PP(0);
	text	   *fc_search_str = PG_GETARG_TEXT_PP(1);

	PG_RETURN_INT32((int32) fc_text_position(fc_str, fc_search_str, PG_GET_COLLATION()));
}

/*
 * text_position -
 *	为textpos()做实际工作
 *
 * 输入：
 *		t1 - 要搜索的字符串
 *		t2 - 在t1中匹配的模式
 * 结果：
 *		第一个匹配字符的字符索引，从1开始，
 *		如果没有匹配则为0。
 *
 *		将其分开，以便可以由其他字符串处理
 *		函数直接调用。
 */
static int fc_text_position(text *fc_t1, text *fc_t2, Oid fc_collid)
{
	TextPositionState fc_state;
	int			fc_result;

	/* 空针始终在位置1匹配 */
	if (VARSIZE_ANY_EXHDR(fc_t2) < 1)
		return 1;

	/* 否则，如果干草堆短于针，则无法匹配 */
	if (VARSIZE_ANY_EXHDR(fc_t1) < VARSIZE_ANY_EXHDR(fc_t2))
		return 0;

	fc_text_position_setup(fc_t1, fc_t2, fc_collid, &fc_state);
	if (!fc_text_position_next(&fc_state))
		fc_result = 0;
	else
		fc_result = fc_text_position_get_match_pos(&fc_state);
	fc_text_position_cleanup(&fc_state);
	return fc_result;
}


/*
 * text_position_setup, text_position_next, text_position_cleanup -
 *	text_position()的组件步骤
 *
 * 这些步骤被分开，以便可以高效地搜索字符串
 * 中同一模式的多个出现。text_position_next可以被
 * 多次调用，每次调用时向下一个匹配推进。
 * text_position_get_match_ptr()和text_position_get_match_pos()分别
 * 返回最近匹配的指针或基于1的字符位置。
 *
 * “状态”变量通常只是调用方的一个局部变量。
 *
 * 注意：text_position_next跳过已匹配的部分。例如，
 * 在“xxx”中搜索“xx”仅返回一次匹配，而不是两次。
 */

static void fc_text_position_setup(text *fc_t1, text *fc_t2, Oid fc_collid, TextPositionState *fc_state)
{
	int			fc_len1 = VARSIZE_ANY_EXHDR(fc_t1);
	int			fc_len2 = VARSIZE_ANY_EXHDR(fc_t2);
	pg_locale_t fc_mylocale = 0;

	fc_check_collation_set(fc_collid);

	if (!lc_collate_is_c(fc_collid))
		fc_mylocale = pg_newlocale_from_collation(fc_collid);

	if (fc_mylocale && !fc_mylocale->deterministic)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("nondeterministic collations are not supported for substring searches")));

	Assert(fc_len1 > 0);
	Assert(fc_len2 > 0);

	/*
	 * 即使在多字节编码下，我们也使用原始字节序列进行搜索，
	 * 忽略多字节问题。对于UTF-8，这很好，因为在UTF-8中，单个字符的字节序列不能包含
	 * 另一个字符。对于其他多字节编码，我们最初将搜索作为简单的字节搜索，
	 * 忽略多字节问题，但随后验证我们找到的匹配是否在字符边界上，
	 * 如果匹配错误，则继续搜索。
	 */
	if (pg_database_encoding_max_length() == 1)
		fc_state->is_multibyte_char_in_char = false;
	else if (GetDatabaseEncoding() == PG_UTF8)
		fc_state->is_multibyte_char_in_char = false;
	else
		fc_state->is_multibyte_char_in_char = true;

	fc_state->str1 = VARDATA_ANY(fc_t1);
	fc_state->str2 = VARDATA_ANY(fc_t2);
	fc_state->len1 = fc_len1;
	fc_state->len2 = fc_len2;
	fc_state->last_match = NULL;
	fc_state->refpoint = fc_state->str1;
	fc_state->refpos = 0;

	/*
	 * 准备 Boyer-Moore-Horspool 搜索的跳过表。在这些笔记中，我们使用术语“干草堆”表示要搜索的字符串（t1），
	 * “针”表示要寻求的模式（t2）。
	 *
	 * 如果针为空或比干草堆大，那么初始化表的循环是没有意义的。我们还选择不对长度为 1 的针使用 B-M-H，
	 * 因为在这种情况下跳过表根本无法节省任何东西。
	 */
	if (fc_len1 >= fc_len2 && fc_len2 > 1)
	{
		int			fc_searchlength = fc_len1 - fc_len2;
		int			fc_skiptablemask;
		int			fc_last;
		int			fc_i;
		const char *fc_str2 = fc_state->str2;

		/*
		 * 首先，我们必须确定要使用多少跳过表。TextPositionState 的声明允许最多 256 个元素，
		 * 但是对于短搜索问题，我们并不想初始化这么多元素——与实际搜索时间相比，这会花费太长时间。
		 * 因此，我们根据干草堆长度减去针长度选择一个有用的跳过表大小。针长度越接近干草堆长度，跳过的效果就越不明显。
		 *
		 * 注意：由于我们使用位掩码选择表元素，跳过表大小必须是 2 的幂，因此掩码必须是 2^N-1。
		 */
		if (fc_searchlength < 16)
			fc_skiptablemask = 3;
		else if (fc_searchlength < 64)
			fc_skiptablemask = 7;
		else if (fc_searchlength < 128)
			fc_skiptablemask = 15;
		else if (fc_searchlength < 512)
			fc_skiptablemask = 31;
		else if (fc_searchlength < 2048)
			fc_skiptablemask = 63;
		else if (fc_searchlength < 4096)
			fc_skiptablemask = 127;
		else
			fc_skiptablemask = 255;
		fc_state->skiptablemask = fc_skiptablemask;

		/*
		 * 初始化跳过表。我们将所有元素设置为针的长度，因为这是未在针中找到的任何字符的正确跳过距离。
		 */
		for (fc_i = 0; fc_i <= fc_skiptablemask; fc_i++)
			fc_state->skiptable[fc_i] = fc_len2;

		/*
		 * 现在检查针。对于除了最后一个字符以外的每个字符，将相应的表元素设置为适当的跳过距离。
		 * 注意，当两个字符共享同一个跳过表条目时，针中后面的字符必须确定跳过距离。
		 */
		fc_last = fc_len2 - 1;

		for (fc_i = 0; fc_i < fc_last; fc_i++)
			fc_state->skiptable[(unsigned char) fc_str2[fc_i] & fc_skiptablemask] = fc_last - fc_i;
	}
}

/*
 * 从上一个匹配的末尾（或首次调用时的字符串开头）开始，向下一个匹配推进。 如果找到匹配，则返回 true。
 *
 * 注意，这拒绝匹配空字符串的指针。大多数调用者将特别处理这种情况，我们在这里不会看到它。
 */
static bool fc_text_position_next(TextPositionState *fc_state)
{
	int			fc_needle_len = fc_state->len2;
	char	   *fc_start_ptr;
	char	   *fc_matchptr;

	if (fc_needle_len <= 0)
		return false;			/* 空模式的结果 */

	/* 从上一个匹配的下一个点开始。 */
	if (fc_state->last_match)
		fc_start_ptr = fc_state->last_match + fc_needle_len;
	else
		fc_start_ptr = fc_state->str1;

retry:
	fc_matchptr = fc_text_position_next_internal(fc_start_ptr, fc_state);

	if (!fc_matchptr)
		return false;

	/* 找到了字节序列的匹配。如果这是一个多字节编码， * 其中一个字符的字节序列可以出现在更长的 * 多字节字符内部，我们需要验证匹配是在字符边界上，而不是在多字节字符的中间。 */
	if (fc_state->is_multibyte_char_in_char)
	{
		/* 每次处理一个字符，直到我们到达匹配。 */

		/* 搜索绝不应向后移动。 */
		Assert(fc_state->refpoint <= fc_matchptr);

		while (fc_state->refpoint < fc_matchptr)
		{
			/* 移动到下一个字符。 */
			fc_state->refpoint += pg_mblen(fc_state->refpoint);
			fc_state->refpos++;

			/* 如果我们越过了匹配的起始位置，那么这是一个 * 假阳性，其中字节序列出现在多字节字符的中间。跳过它，并在下一个字符边界继续搜索。 */
			if (fc_state->refpoint > fc_matchptr)
			{
				fc_start_ptr = fc_state->refpoint;
				goto retry;
			}
		}
	}

	fc_state->last_match = fc_matchptr;
	return true;
}

/* text_position_next() 的子例程。此例程搜索原始字节序列， 忽略任何多字节编码问题。返回从 'start_ptr' 开始的第一个匹配， 如果未找到匹配，则返回 NULL。 */
static char * fc_text_position_next_internal(char *fc_start_ptr, TextPositionState *fc_state)
{
	int			fc_haystack_len = fc_state->len1;
	int			fc_needle_len = fc_state->len2;
	int			fc_skiptablemask = fc_state->skiptablemask;
	const char *fc_haystack = fc_state->str1;
	const char *fc_needle = fc_state->str2;
	const char *fc_haystack_end = &fc_haystack[fc_haystack_len];
	const char *fc_hptr;

	Assert(fc_start_ptr >= fc_haystack && fc_start_ptr <= fc_haystack_end);

	if (fc_needle_len == 1)
	{
		/* 对于一个字符的针，使用 B-M-H 并没有意义 */
		char		fc_nchar = *fc_needle;

		fc_hptr = fc_start_ptr;
		while (fc_hptr < fc_haystack_end)
		{
			if (*fc_hptr == fc_nchar)
				return (char *) fc_hptr;
			fc_hptr++;
		}
	}
	else
	{
		const char *fc_needle_last = &fc_needle[fc_needle_len - 1];

		/* 从 startpos 加上针的长度开始 */
		fc_hptr = fc_start_ptr + fc_needle_len - 1;
		while (fc_hptr < fc_haystack_end)
		{
			/* 反向扫描匹配针 */
			const char *fc_nptr;
			const char *fc_p;

			fc_nptr = fc_needle_last;
			fc_p = fc_hptr;
			while (*fc_nptr == *fc_p)
			{
				/* 全部匹配了吗？如果是，返回基于 1 的位置 */
				if (fc_nptr == fc_needle)
					return (char *) fc_p;
				fc_nptr--, fc_p--;
			}

			/* 没有匹配，因此使用 haystack 字符 hptr 来决定前进多远。 如果针的最后字符之前有该字符的任何出现（或者更准确地说，有一 个共享相同跳过表条目的字符），那么我们向前推进足够的距离以对齐最后的这样针字符与该 haystack 位置。 否则，我们可以前进整个针的长度。 */
			fc_hptr += fc_state->skiptable[(unsigned char) *fc_hptr & fc_skiptablemask];
		}
	}

	return 0;					/* 未找到 */
}

/* 返回当前匹配的指针。 * * 返回的指针指向原始的 haystack 字符串。 */
static char * fc_text_position_get_match_ptr(TextPositionState *fc_state)
{
	return fc_state->last_match;
}

/* 返回当前匹配的偏移量。 * * 偏移量以字符为单位，基于 1。 */
static int fc_text_position_get_match_pos(TextPositionState *fc_state)
{
	/* 将字节位置转换为字符位置。 */
	fc_state->refpos += pg_mbstrlen_with_len(fc_state->refpoint,
										  fc_state->last_match - fc_state->refpoint);
	fc_state->refpoint = fc_state->last_match;
	return fc_state->refpos + 1;
}

/* 将搜索状态重置为由 text_position_setup 安装的初始状态。 * * 接下来的 text_position_next 调用将从字符串的开头开始搜索。 */
static void fc_text_position_reset(TextPositionState *fc_state)
{
	fc_state->last_match = NULL;
	fc_state->refpoint = fc_state->str1;
	fc_state->refpos = 0;
}

static void fc_text_position_cleanup(TextPositionState *fc_state)
{
	/* 无需清理 */
}


static void fc_check_collation_set(Oid fc_collid)
{
	if (!OidIsValid(fc_collid))
	{
		/*
		 * 这通常意味着解析器无法解决隐式排序的冲突，
		 * 因此以这种方式报告。
		 */
		ereport(ERROR,
				(errcode(ERRCODE_INDETERMINATE_COLLATION),
				 errmsg("could not determine which collation to use for string comparison"),
				 errhint("Use the COLLATE clause to set the collation explicitly.")));
	}
}

/* varstr_cmp() * 用于具有给定长度的文本字符串的比较函数。 * 包括语言环境支持，但必须将字符串复制到临时内存 * 以允许输入的 strcoll() 进行空终止。 * 返回一个小于、等于或大于零的整数，指示 * arg1 是小于、等于还是大于 arg2。 * * 注意：依赖于此的许多函数被标记为防泄漏；因此， * 在抛出错误时应避免报告输入的实际内容。 * 此处的所有错误都应该是除了在损坏数据上之外不可能发生的事情；否则，我们将处理索引字符串的问题，可能会导致它们。 */
int varstr_cmp(const char *fc_arg1, int fc_len1, const char *fc_arg2, int fc_len2, Oid fc_collid)
{
	int			fc_result;

	fc_check_collation_set(fc_collid);

	/* 不幸的是，没有 strncoll()，因此在非 C 语言环境的情况下， * 我们必须进行一些内存复制。这被证明是显著 * 更慢，因此我们优化 LC_COLLATE 为 C 的情况。我们还尝试 * 通过避免 palloc/pfree 的开销来优化相对较短的字符串。 */
	if (lc_collate_is_c(fc_collid))
	{
		fc_result = memcmp(fc_arg1, fc_arg2, Min(fc_len1, fc_len2));
		if ((fc_result == 0) && (fc_len1 != fc_len2))
			fc_result = (fc_len1 < fc_len2) ? -1 : 1;
	}
	else
	{
		char		fc_a1buf[TEXTBUFLEN];
		char		fc_a2buf[TEXTBUFLEN];
		char	   *fc_a1p,
				   *fc_a2p;
		pg_locale_t fc_mylocale;

		fc_mylocale = pg_newlocale_from_collation(fc_collid);

		/* memcmp() 无法告诉我们两个不等字符串的排序顺序， * 但这是判断它们是否相等的廉价方法。测试表明， * memcmp() 后跟 strcoll() 仅略慢于单独的 strcoll()，因此如果这不经常出现，我们也不会失去太多，如果出现 - 例如，因为输入中有许多 * 相等字符串 - 那么我们可以通过避免昂贵的 * 整体排序比较来获得巨大的收益。 */
		if (fc_len1 == fc_len2 && memcmp(fc_arg1, fc_arg2, fc_len1) == 0)
			return 0;

#ifdef WIN32
		/* Win32 不支持 UTF-8，因此我们需要映射到 UTF-16 */
		if (GetDatabaseEncoding() == PG_UTF8
			&& (!fc_mylocale || fc_mylocale->provider == COLLPROVIDER_LIBC))
		{
			int			a1len;
			int			a2len;
			int			r;

			if (fc_len1 >= TEXTBUFLEN / 2)
			{
				a1len = fc_len1 * 2 + 2;
				fc_a1p = palloc(a1len);
			}
			else
			{
				a1len = TEXTBUFLEN;
				fc_a1p = a1buf;
			}
			if (fc_len2 >= TEXTBUFLEN / 2)
			{
				a2len = fc_len2 * 2 + 2;
				fc_a2p = palloc(a2len);
			}
			else
			{
				a2len = TEXTBUFLEN;
				fc_a2p = a2buf;
			}

			/* 愚蠢的 Microsloth API 不适用于零长度输入 */
			if (fc_len1 == 0)
				r = 0;
			else
			{
				r = MultiByteToWideChar(CP_UTF8, 0, fc_arg1, fc_len1,
										(LPWSTR) fc_a1p, a1len / 2);
				if (!r)
					ereport(ERROR,
							(errmsg("could not convert string to UTF-16: error code %lu",
									GetLastError())));
			}
			((LPWSTR) fc_a1p)[r] = 0;

			if (fc_len2 == 0)
				r = 0;
			else
			{
				r = MultiByteToWideChar(CP_UTF8, 0, fc_arg2, fc_len2,
										(LPWSTR) fc_a2p, a2len / 2);
				if (!r)
					ereport(ERROR,
							(errmsg("could not convert string to UTF-16: error code %lu",
									GetLastError())));
			}
			((LPWSTR) fc_a2p)[r] = 0;

			errno = 0;
#ifdef HAVE_LOCALE_T
			if (fc_mylocale)
				fc_result = wcscoll_l((LPWSTR) fc_a1p, (LPWSTR) fc_a2p, fc_mylocale->info.lt);
			else
#endif
				fc_result = wcscoll((LPWSTR) fc_a1p, (LPWSTR) fc_a2p);
			if (fc_result == 2147483647)	/* _NLSCMPERROR；在 mingw 头文件中缺失 */
				ereport(ERROR,
						(errmsg("could not compare Unicode strings: %m")));

			/* 如有必要，打破平局。 */
			if (fc_result == 0 &&
				(!fc_mylocale || fc_mylocale->deterministic))
			{
				fc_result = memcmp(fc_arg1, fc_arg2, Min(fc_len1, fc_len2));
				if ((fc_result == 0) && (fc_len1 != fc_len2))
					fc_result = (fc_len1 < fc_len2) ? -1 : 1;
			}

			if (fc_a1p != a1buf)
				pfree(fc_a1p);
			if (fc_a2p != a2buf)
				pfree(fc_a2p);

			return fc_result;
		}
#endif							/* WIN32 */

		if (fc_len1 >= TEXTBUFLEN)
			fc_a1p = (char *) palloc(fc_len1 + 1);
		else
			fc_a1p = fc_a1buf;
		if (fc_len2 >= TEXTBUFLEN)
			fc_a2p = (char *) palloc(fc_len2 + 1);
		else
			fc_a2p = fc_a2buf;

		memcpy(fc_a1p, fc_arg1, fc_len1);
		fc_a1p[fc_len1] = '\0';
		memcpy(fc_a2p, fc_arg2, fc_len2);
		fc_a2p[fc_len2] = '\0';

		if (fc_mylocale)
		{
			if (fc_mylocale->provider == COLLPROVIDER_ICU)
			{
#ifdef USE_ICU
#ifdef HAVE_UCOL_STRCOLLUTF8
				if (GetDatabaseEncoding() == PG_UTF8)
				{
					UErrorCode	fc_status;

					fc_status = U_ZERO_ERROR;
					fc_result = ucol_strcollUTF8(fc_mylocale->info.icu.ucol,
											  fc_arg1, fc_len1,
											  fc_arg2, fc_len2,
											  &fc_status);
					if (U_FAILURE(fc_status))
						ereport(ERROR,
								(errmsg("collation failed: %s", u_errorName(fc_status))));
				}
				else
#endif
				{
					int32_t		fc_ulen1,
								fc_ulen2;
					UChar	   *fc_uchar1,
							   *fc_uchar2;

					fc_ulen1 = icu_to_uchar(&fc_uchar1, fc_arg1, fc_len1);
					fc_ulen2 = icu_to_uchar(&fc_uchar2, fc_arg2, fc_len2);

					fc_result = ucol_strcoll(fc_mylocale->info.icu.ucol,
										  fc_uchar1, fc_ulen1,
										  fc_uchar2, fc_ulen2);

					pfree(fc_uchar1);
					pfree(fc_uchar2);
				}
#else							/* 不使用 USE_ICU */
				/* 不应该发生 */
				elog(ERROR, "unsupported collprovider: %c", fc_mylocale->provider);
#endif							/* 不使用 USE_ICU */
			}
			else
			{
#ifdef HAVE_LOCALE_T
				fc_result = strcoll_l(fc_a1p, fc_a2p, fc_mylocale->info.lt);
#else
				/* 不应该发生 */
				elog(ERROR, "unsupported collprovider: %c", fc_mylocale->provider);
#endif
			}
		}
		else
			fc_result = strcoll(fc_a1p, fc_a2p);

		/* 如有必要，打破平局。 */
		if (fc_result == 0 &&
			(!fc_mylocale || fc_mylocale->deterministic))
			fc_result = strcmp(fc_a1p, fc_a2p);

		if (fc_a1p != fc_a1buf)
			pfree(fc_a1p);
		if (fc_a2p != fc_a2buf)
			pfree(fc_a2p);
	}

	return fc_result;
}

/* text_cmp() * 文本字符串的内部比较函数。 * 返回 -1、0 或 1 */
static int fc_text_cmp(text *fc_arg1, text *fc_arg2, Oid fc_collid)
{
	char	   *fc_a1p,
			   *fc_a2p;
	int			fc_len1,
				fc_len2;

	fc_a1p = VARDATA_ANY(fc_arg1);
	fc_a2p = VARDATA_ANY(fc_arg2);

	fc_len1 = VARSIZE_ANY_EXHDR(fc_arg1);
	fc_len2 = VARSIZE_ANY_EXHDR(fc_arg2);

	return varstr_cmp(fc_a1p, fc_len1, fc_a2p, fc_len2, fc_collid);
}

/* 文本字符串的比较函数。 * * 注意：btree 索引需要这些例程不要泄漏内存；因此， * 小心释放托管值的工作副本。大多数地方并不需要如此小心。 */

Datum texteq(PG_FUNCTION_ARGS)
{
	Oid			fc_collid = PG_GET_COLLATION();
	bool		fc_locale_is_c = false;
	pg_locale_t fc_mylocale = 0;
	bool		fc_result;

	fc_check_collation_set(fc_collid);

	if (lc_collate_is_c(fc_collid))
		fc_locale_is_c = true;
	else
		fc_mylocale = pg_newlocale_from_collation(fc_collid);

	if (fc_locale_is_c || !fc_mylocale || fc_mylocale->deterministic)
	{
		Datum		fc_arg1 = PG_GETARG_DATUM(0);
		Datum		fc_arg2 = PG_GETARG_DATUM(1);
		Size		fc_len1,
					fc_len2;

		/* 由于我们只关心相等性或不相等性，我们可以避免所有的 strcoll() 的费用， * 而只进行逐位比较。事实上，如果我们可以证明 * 字符串的长度不相等，那么我们甚至不需要进行逐位比较；这可能会避免我们必须处理一个或两个值的情况。 */
		fc_len1 = toast_raw_datum_size(fc_arg1);
		fc_len2 = toast_raw_datum_size(fc_arg2);
		if (fc_len1 != fc_len2)
			fc_result = false;
		else
		{
			text	   *fc_targ1 = DatumGetTextPP(fc_arg1);
			text	   *fc_targ2 = DatumGetTextPP(fc_arg2);

			fc_result = (memcmp(VARDATA_ANY(fc_targ1), VARDATA_ANY(fc_targ2),
							 fc_len1 - VARHDRSZ) == 0);

			PG_FREE_IF_COPY(fc_targ1, 0);
			PG_FREE_IF_COPY(fc_targ2, 1);
		}
	}
	else
	{
		text	   *fc_arg1 = PG_GETARG_TEXT_PP(0);
		text	   *fc_arg2 = PG_GETARG_TEXT_PP(1);

		fc_result = (fc_text_cmp(fc_arg1, fc_arg2, fc_collid) == 0);

		PG_FREE_IF_COPY(fc_arg1, 0);
		PG_FREE_IF_COPY(fc_arg2, 1);
	}

	PG_RETURN_BOOL(fc_result);
}

Datum textne(PG_FUNCTION_ARGS)
{
	Oid			fc_collid = PG_GET_COLLATION();
	bool		fc_locale_is_c = false;
	pg_locale_t fc_mylocale = 0;
	bool		fc_result;

	fc_check_collation_set(fc_collid);

	if (lc_collate_is_c(fc_collid))
		fc_locale_is_c = true;
	else
		fc_mylocale = pg_newlocale_from_collation(fc_collid);

	if (fc_locale_is_c || !fc_mylocale || fc_mylocale->deterministic)
	{
		Datum		fc_arg1 = PG_GETARG_DATUM(0);
		Datum		fc_arg2 = PG_GETARG_DATUM(1);
		Size		fc_len1,
					fc_len2;

		/* 请参阅 texteq() 中的注释 */
		fc_len1 = toast_raw_datum_size(fc_arg1);
		fc_len2 = toast_raw_datum_size(fc_arg2);
		if (fc_len1 != fc_len2)
			fc_result = true;
		else
		{
			text	   *fc_targ1 = DatumGetTextPP(fc_arg1);
			text	   *fc_targ2 = DatumGetTextPP(fc_arg2);

			fc_result = (memcmp(VARDATA_ANY(fc_targ1), VARDATA_ANY(fc_targ2),
							 fc_len1 - VARHDRSZ) != 0);

			PG_FREE_IF_COPY(fc_targ1, 0);
			PG_FREE_IF_COPY(fc_targ2, 1);
		}
	}
	else
	{
		text	   *fc_arg1 = PG_GETARG_TEXT_PP(0);
		text	   *fc_arg2 = PG_GETARG_TEXT_PP(1);

		fc_result = (fc_text_cmp(fc_arg1, fc_arg2, fc_collid) != 0);

		PG_FREE_IF_COPY(fc_arg1, 0);
		PG_FREE_IF_COPY(fc_arg2, 1);
	}

	PG_RETURN_BOOL(fc_result);
}

Datum text_lt(PG_FUNCTION_ARGS)
{
	text	   *fc_arg1 = PG_GETARG_TEXT_PP(0);
	text	   *fc_arg2 = PG_GETARG_TEXT_PP(1);
	bool		fc_result;

	fc_result = (fc_text_cmp(fc_arg1, fc_arg2, PG_GET_COLLATION()) < 0);

	PG_FREE_IF_COPY(fc_arg1, 0);
	PG_FREE_IF_COPY(fc_arg2, 1);

	PG_RETURN_BOOL(fc_result);
}

Datum text_le(PG_FUNCTION_ARGS)
{
	text	   *fc_arg1 = PG_GETARG_TEXT_PP(0);
	text	   *fc_arg2 = PG_GETARG_TEXT_PP(1);
	bool		fc_result;

	fc_result = (fc_text_cmp(fc_arg1, fc_arg2, PG_GET_COLLATION()) <= 0);

	PG_FREE_IF_COPY(fc_arg1, 0);
	PG_FREE_IF_COPY(fc_arg2, 1);

	PG_RETURN_BOOL(fc_result);
}

Datum text_gt(PG_FUNCTION_ARGS)
{
	text	   *fc_arg1 = PG_GETARG_TEXT_PP(0);
	text	   *fc_arg2 = PG_GETARG_TEXT_PP(1);
	bool		fc_result;

	fc_result = (fc_text_cmp(fc_arg1, fc_arg2, PG_GET_COLLATION()) > 0);

	PG_FREE_IF_COPY(fc_arg1, 0);
	PG_FREE_IF_COPY(fc_arg2, 1);

	PG_RETURN_BOOL(fc_result);
}

Datum text_ge(PG_FUNCTION_ARGS)
{
	text	   *fc_arg1 = PG_GETARG_TEXT_PP(0);
	text	   *fc_arg2 = PG_GETARG_TEXT_PP(1);
	bool		fc_result;

	fc_result = (fc_text_cmp(fc_arg1, fc_arg2, PG_GET_COLLATION()) >= 0);

	PG_FREE_IF_COPY(fc_arg1, 0);
	PG_FREE_IF_COPY(fc_arg2, 1);

	PG_RETURN_BOOL(fc_result);
}

Datum text_starts_with(PG_FUNCTION_ARGS)
{
	Datum		fc_arg1 = PG_GETARG_DATUM(0);
	Datum		fc_arg2 = PG_GETARG_DATUM(1);
	Oid			fc_collid = PG_GET_COLLATION();
	pg_locale_t fc_mylocale = 0;
	bool		fc_result;
	Size		fc_len1,
				fc_len2;

	fc_check_collation_set(fc_collid);

	if (!lc_collate_is_c(fc_collid))
		fc_mylocale = pg_newlocale_from_collation(fc_collid);

	if (fc_mylocale && !fc_mylocale->deterministic)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("nondeterministic collations are not supported for substring searches")));

	fc_len1 = toast_raw_datum_size(fc_arg1);
	fc_len2 = toast_raw_datum_size(fc_arg2);
	if (fc_len2 > fc_len1)
		fc_result = false;
	else
	{
		text	   *fc_targ1 = fc_text_substring(fc_arg1, 1, fc_len2, false);
		text	   *fc_targ2 = DatumGetTextPP(fc_arg2);

		fc_result = (memcmp(VARDATA_ANY(fc_targ1), VARDATA_ANY(fc_targ2),
						 VARSIZE_ANY_EXHDR(fc_targ2)) == 0);

		PG_FREE_IF_COPY(fc_targ1, 0);
		PG_FREE_IF_COPY(fc_targ2, 1);
	}

	PG_RETURN_BOOL(fc_result);
}

Datum bttextcmp(PG_FUNCTION_ARGS)
{
	text	   *fc_arg1 = PG_GETARG_TEXT_PP(0);
	text	   *fc_arg2 = PG_GETARG_TEXT_PP(1);
	int32		fc_result;

	fc_result = fc_text_cmp(fc_arg1, fc_arg2, PG_GET_COLLATION());

	PG_FREE_IF_COPY(fc_arg1, 0);
	PG_FREE_IF_COPY(fc_arg2, 1);

	PG_RETURN_INT32(fc_result);
}

Datum bttextsortsupport(PG_FUNCTION_ARGS)
{
	SortSupport fc_ssup = (SortSupport) PG_GETARG_POINTER(0);
	Oid			fc_collid = fc_ssup->ssup_collation;
	MemoryContext fc_oldcontext;

	fc_oldcontext = MemoryContextSwitchTo(fc_ssup->ssup_cxt);

	/* 使用通用字符串 SortSupport */
	varstr_sortsupport(fc_ssup, TEXTOID, fc_collid);

	MemoryContextSwitchTo(fc_oldcontext);

	PG_RETURN_VOID();
}


/*
 * 通用排序支持接口，用于字符类型的运算符类。
 * 包括区域设置支持，以及支持BpChar语义（即在比较前去除尾随空格）。
 *
 * 基于文本、VarChar、BpChar和bytea具有相同表示的假设。 始终使用C排序规则的调用者（例如像bytea这样的非可排序类型调用者）可能在其字符串中包含NUL字节；不过，这与任何其他排序规则都不兼容。
 */
void varstr_sortsupport(SortSupport fc_ssup, Oid fc_typid, Oid fc_collid)
{
	bool		fc_abbreviate = fc_ssup->abbreviate;
	bool		fc_collate_c = false;
	VarStringSortSupport *fc_sss;
	pg_locale_t fc_locale = 0;

	fc_check_collation_set(fc_collid);

	/*
	 * 如果可能，将ssup->comparator设置为可以直接比较
	 * 两个datum的函数。如果我们能做到这一点，我们将避免在
	 * 每次比较时通过fmgr层的开销，这可能是相当可观的。
	 *
	 * 通常情况下，我们将比较器设置为varlenafastcmp_locale，
	 * 该函数使用strcoll()进行比较。我们在BpChar情况下也使用
	 * 这个，但是类型NAME使用namefastcmp_locale。然而，如果
	 * LC_COLLATE = C，我们可以通过varstrfastcmp_c、bpcharfastcmp_c或namefastcmp_c将速度大大提高，因为它们使用
	 * memcmp()而不是strcoll()。
	 */
	if (lc_collate_is_c(fc_collid))
	{
		if (fc_typid == BPCHAROID)
			fc_ssup->comparator = fc_bpcharfastcmp_c;
		else if (fc_typid == NAMEOID)
		{
			fc_ssup->comparator = fc_namefastcmp_c;
			/* 目前不支持类型 NAME 的缩写 */
			fc_abbreviate = false;
		}
		else
			fc_ssup->comparator = fc_varstrfastcmp_c;

		fc_collate_c = true;
	}
	else
	{
		/*
		 * 我们需要进行区分符敏感的比较。为了加快速度，
		 * 我们将根据区域 ID 确定排序规则并缓存结果。
		 */
		fc_locale = pg_newlocale_from_collation(fc_collid);

		/*
		 * 在 Windows 上有一个进一步的例外。当数据库
		 * 编码为 UTF-8 且我们不使用 C 排序规则时，需要复杂的
		 * 黑客手段。我们目前没有处理这种情况的比较器，
		 * 因此我们回退到慢速方法，让排序代码通过 fmgr
		 * 跳板调用 bttextcmp()（在文本情况下）。然而，
		 * ICU 区域设置在 Windows 上的工作方式是一样的。
		 */
#ifdef WIN32
		if (GetDatabaseEncoding() == PG_UTF8 &&
			!(locale && locale->provider == COLLPROVIDER_ICU))
			return;
#endif

		/*
		 * 除了类型 NAME，我们使用 varlenafastcmp_locale。
		 */
		if (fc_typid == NAMEOID)
		{
			fc_ssup->comparator = fc_namefastcmp_locale;
			/* 目前不支持类型 NAME 的缩写 */
			fc_abbreviate = false;
		}
		else
			fc_ssup->comparator = fc_varlenafastcmp_locale;
	}

	/*
	 * 不幸的是，非 C 排序规则的缩写在许多常见平台上似乎
	 * 是破损的；对多个版本的 glibc 的测试显示，对于许多
	 * 区域，strcoll() 和 strxfrm() 的结果并不一致，
	 * 这对这种优化是致命的。到目前为止，除了 Cygwin
	 * 之外，没有其他 libc 显示出问题，因此我们采取
	 * 保守的行动方案，目前直接禁用它。（确定其系统上
	 * 不是问题的用户可以定义 TRUST_STRXFRM。）
	 *
	 * 即使不考虑破损区域的风险，可能还有一些平台
	 * 在编译时应禁用使用缩写键。例如，只有 4 字节的
	 * 数据可能会显著增加最坏情况的性能。此外，macOS 的
	 * strxfrm() 实现已知不会有效集中来自原始字符串的
	 * 重大熵在早期转换的 blob 中。可能其他支持的平台
	 * 也同样受到影响。因此，如果我们最终能超越
	 * 直接禁用它，我们可能仍然想或需要为特定平台
	 * 禁用它。
	 */
#ifndef TRUST_STRXFRM
	if (!fc_collate_c && !(fc_locale && fc_locale->provider == COLLPROVIDER_ICU))
		fc_abbreviate = false;
#endif

	/*
	 * 如果我们正在使用缩写键，或如果我们使用意识到
	 * 区域的比较，我们需要初始化一个 VarStringSortSupport 对象。
	 * 两种情况都会利用我们在这里初始化的临时缓冲区
	 * 作暂存空间（以及检测调用者所需的 BpChar 语义），
	 * 而缩写情况需要额外的状态。
	 */
	if (fc_abbreviate || !fc_collate_c)
	{
		fc_sss = palloc(sizeof(VarStringSortSupport));
		fc_sss->buf1 = palloc(TEXTBUFLEN);
		fc_sss->buflen1 = TEXTBUFLEN;
		fc_sss->buf2 = palloc(TEXTBUFLEN);
		fc_sss->buflen2 = TEXTBUFLEN;
		/* 从无效值开始 */
		fc_sss->last_len1 = -1;
		fc_sss->last_len2 = -1;
		/* 初始化 */
		fc_sss->last_returned = 0;
		fc_sss->locale = fc_locale;

		/*
		 * 为了避免在某种程度上混淆 strxfrm() blob 和原始字符串，
		 * 不断跟踪 buf1 和 buf2 中当前包含的数据种类。
		 *
		 * 比较可能与转换调用交错处理。通常，
		 * 转换和比较被分为两个不同的阶段，但缓存的正确性
		 * 不能依赖于此。对于比较缓存，只有当 cache_blob
		 * 被找到设置为 false 时，缓冲区状态才是可信的，
		 * 而 strxfrm() 缓存仅在 cache_blob 被找到
		 * 设置为 true 时信任该状态。
		 *
		 * 随意将 cache_blob 初始化为 true。
		 */
		fc_sss->cache_blob = true;
		fc_sss->collate_c = fc_collate_c;
		fc_sss->typid = fc_typid;
		fc_ssup->ssup_extra = fc_sss;

		/*
		 * 如果可能，计划使用缩写键优化。如果
		 * 缩写被中止，核心代码可能会切换回权威比较器。
		 */
		if (fc_abbreviate)
		{
			fc_sss->prop_card = 0.20;
			initHyperLogLog(&fc_sss->abbr_card, 10);
			initHyperLogLog(&fc_sss->full_card, 10);
			fc_ssup->abbrev_full_comparator = fc_ssup->comparator;
			fc_ssup->comparator = ssup_datum_unsigned_cmp;
			fc_ssup->abbrev_converter = fc_varstr_abbrev_convert;
			fc_ssup->abbrev_abort = fc_varstr_abbrev_abort;
		}
	}
}

/*
 * sortsupport 比较函数（针对 C 区域的情况）
 */
static int fc_varstrfastcmp_c(Datum fc_x, Datum fc_y, SortSupport fc_ssup)
{
	VarString  *fc_arg1 = DatumGetVarStringPP(fc_x);
	VarString  *fc_arg2 = DatumGetVarStringPP(fc_y);
	char	   *fc_a1p,
			   *fc_a2p;
	int			fc_len1,
				fc_len2,
				fc_result;

	fc_a1p = VARDATA_ANY(fc_arg1);
	fc_a2p = VARDATA_ANY(fc_arg2);

	fc_len1 = VARSIZE_ANY_EXHDR(fc_arg1);
	fc_len2 = VARSIZE_ANY_EXHDR(fc_arg2);

	fc_result = memcmp(fc_a1p, fc_a2p, Min(fc_len1, fc_len2));
	if ((fc_result == 0) && (fc_len1 != fc_len2))
		fc_result = (fc_len1 < fc_len2) ? -1 : 1;

	/* 我们不能在这里泄露内存。 */
	if (PointerGetDatum(fc_arg1) != fc_x)
		pfree(fc_arg1);
	if (PointerGetDatum(fc_arg2) != fc_y)
		pfree(fc_arg2);

	return fc_result;
}

/*
 * sortsupport 比较函数（针对 BpChar C 区域情况）
 *
 * BpChar 将其 sortsupport 外包给此模块。针对
 * varstr_sortsupport BpChar 情况的特化，基于
 * internal_bpchar_pattern_compare()。
 */
static int fc_bpcharfastcmp_c(Datum fc_x, Datum fc_y, SortSupport fc_ssup)
{
	BpChar	   *fc_arg1 = DatumGetBpCharPP(fc_x);
	BpChar	   *fc_arg2 = DatumGetBpCharPP(fc_y);
	char	   *fc_a1p,
			   *fc_a2p;
	int			fc_len1,
				fc_len2,
				fc_result;

	fc_a1p = VARDATA_ANY(fc_arg1);
	fc_a2p = VARDATA_ANY(fc_arg2);

	fc_len1 = bpchartruelen(fc_a1p, VARSIZE_ANY_EXHDR(fc_arg1));
	fc_len2 = bpchartruelen(fc_a2p, VARSIZE_ANY_EXHDR(fc_arg2));

	fc_result = memcmp(fc_a1p, fc_a2p, Min(fc_len1, fc_len2));
	if ((fc_result == 0) && (fc_len1 != fc_len2))
		fc_result = (fc_len1 < fc_len2) ? -1 : 1;

	/* 我们不能在这里泄露内存。 */
	if (PointerGetDatum(fc_arg1) != fc_x)
		pfree(fc_arg1);
	if (PointerGetDatum(fc_arg2) != fc_y)
		pfree(fc_arg2);

	return fc_result;
}

/*
 * sortsupport 比较函数（针对 NAME C 区域情况）
 */
static int fc_namefastcmp_c(Datum fc_x, Datum fc_y, SortSupport fc_ssup)
{
	Name		fc_arg1 = DatumGetName(fc_x);
	Name		fc_arg2 = DatumGetName(fc_y);

	return strncmp(NameStr(*fc_arg1), NameStr(*fc_arg2), NAMEDATALEN);
}

/*
 * sortsupport 比较函数（针对所有 varlena 类型的区域情况）
 */
static int fc_varlenafastcmp_locale(Datum fc_x, Datum fc_y, SortSupport fc_ssup)
{
	VarString  *fc_arg1 = DatumGetVarStringPP(fc_x);
	VarString  *fc_arg2 = DatumGetVarStringPP(fc_y);
	char	   *fc_a1p,
			   *fc_a2p;
	int			fc_len1,
				fc_len2,
				fc_result;

	fc_a1p = VARDATA_ANY(fc_arg1);
	fc_a2p = VARDATA_ANY(fc_arg2);

	fc_len1 = VARSIZE_ANY_EXHDR(fc_arg1);
	fc_len2 = VARSIZE_ANY_EXHDR(fc_arg2);

	fc_result = fc_varstrfastcmp_locale(fc_a1p, fc_len1, fc_a2p, fc_len2, fc_ssup);

	/* 我们不能在这里泄露内存。 */
	if (PointerGetDatum(fc_arg1) != fc_x)
		pfree(fc_arg1);
	if (PointerGetDatum(fc_arg2) != fc_y)
		pfree(fc_arg2);

	return fc_result;
}

/*
 * sortsupport 比较函数（针对包含 NAME 类型的区域情况）
 */
static int fc_namefastcmp_locale(Datum fc_x, Datum fc_y, SortSupport fc_ssup)
{
	Name		fc_arg1 = DatumGetName(fc_x);
	Name		fc_arg2 = DatumGetName(fc_y);

	return fc_varstrfastcmp_locale(NameStr(*fc_arg1), strlen(NameStr(*fc_arg1)),
								NameStr(*fc_arg2), strlen(NameStr(*fc_arg2)),
								fc_ssup);
}

/*
 * sortsupport 比较函数，针对区域情况
 */
static int fc_varstrfastcmp_locale(char *fc_a1p, int fc_len1, char *fc_a2p, int fc_len2, SortSupport fc_ssup)
{
	VarStringSortSupport *fc_sss = (VarStringSortSupport *) fc_ssup->ssup_extra;
	int			fc_result;
	bool		fc_arg1_match;

	/* 如 varstr_cmp() 中所述，快速的相等性预检查 */
	if (fc_len1 == fc_len2 && memcmp(fc_a1p, fc_a2p, fc_len1) == 0)
	{
		/*
		 * buf1 或 buf2 内容没有变化，因此避免
		 * 更改 last_len1 或 last_len2。缓冲区的现有
		 * 内容仍可能被下一个调用使用。
		 *
		 * 这里允许比较 BpChar 填充字节是可以的，
		 * 尽管这意味着 memcmp() 通常会被 BpChar 调用者
		 * 执行（尽管多字节字符仍可能阻止这种情况发生）。
		 * memcmp() 仍然非常便宜，而且 BpChar 奇特的
		 * 语义使我们删除尾随空格（不限于填充），
		 * 所以我们不需要在填充空格字符和“真实”的
		 * 空格字符之间做任何区分。
		 */
		return 0;
	}

	if (fc_sss->typid == BPCHAROID)
	{
		/* 获取真实字节数，忽略尾随空格 */
		fc_len1 = bpchartruelen(fc_a1p, fc_len1);
		fc_len2 = bpchartruelen(fc_a2p, fc_len2);
	}

	if (fc_len1 >= fc_sss->buflen1)
	{
		fc_sss->buflen1 = Max(fc_len1 + 1, Min(fc_sss->buflen1 * 2, MaxAllocSize));
		fc_sss->buf1 = repalloc(fc_sss->buf1, fc_sss->buflen1);
	}
	if (fc_len2 >= fc_sss->buflen2)
	{
		fc_sss->buflen2 = Max(fc_len2 + 1, Min(fc_sss->buflen2 * 2, MaxAllocSize));
		fc_sss->buf2 = repalloc(fc_sss->buf2, fc_sss->buflen2);
	}

	
/*
	 * 我们可能会被要求重复比较相同的字符串，而
	 * memcmp() 的开销远低于 strcoll()，因此值得尝试缓存
	 * 比较，尽管通常没有理由认为这能奏效（每个字符串数据可能是唯一的）。缓存
	 * 在没有奏效时不会显著减慢速度，而在奏效时可以大幅提速。部分原因在于
	 * memcmp() 比较来自缓存线的数据，这些缓存线在 L1 缓存中是需要的，即使
	 * 上一个比较的结果无法重用。
	 */
	fc_arg1_match = true;
	if (fc_len1 != fc_sss->last_len1 || memcmp(fc_sss->buf1, fc_a1p, fc_len1) != 0)
	{
		fc_arg1_match = false;
		memcpy(fc_sss->buf1, fc_a1p, fc_len1);
		fc_sss->buf1[fc_len1] = '\0';
		fc_sss->last_len1 = fc_len1;
	}

	/*
	 * 如果我们在比较与上次相同的两个字符串，便可以返回
	 * 相同的答案，而无需再次调用 strcoll()。这比看起来更有可能
	 * （至少在中等到低基数集合中），因为快速排序将同一个主元与多个值进行比较。
	 */
	if (fc_len2 != fc_sss->last_len2 || memcmp(fc_sss->buf2, fc_a2p, fc_len2) != 0)
	{
		memcpy(fc_sss->buf2, fc_a2p, fc_len2);
		fc_sss->buf2[fc_len2] = '\0';
		fc_sss->last_len2 = fc_len2;
	}
	else if (fc_arg1_match && !fc_sss->cache_blob)
	{
		/* 使用上一次实际 strcoll() 调用后缓存的结果 */
		return fc_sss->last_returned;
	}

	if (fc_sss->locale)
	{
		if (fc_sss->locale->provider == COLLPROVIDER_ICU)
		{
#ifdef USE_ICU
#ifdef HAVE_UCOL_STRCOLLUTF8
			if (GetDatabaseEncoding() == PG_UTF8)
			{
				UErrorCode	fc_status;

				fc_status = U_ZERO_ERROR;
				fc_result = ucol_strcollUTF8(fc_sss->locale->info.icu.ucol,
										  fc_a1p, fc_len1,
										  fc_a2p, fc_len2,
										  &fc_status);
				if (U_FAILURE(fc_status))
					ereport(ERROR,
							(errmsg("collation failed: %s", u_errorName(fc_status))));
			}
			else
#endif
			{
				int32_t		fc_ulen1,
							fc_ulen2;
				UChar	   *fc_uchar1,
						   *fc_uchar2;

				fc_ulen1 = icu_to_uchar(&fc_uchar1, fc_a1p, fc_len1);
				fc_ulen2 = icu_to_uchar(&fc_uchar2, fc_a2p, fc_len2);

				fc_result = ucol_strcoll(fc_sss->locale->info.icu.ucol,
									  fc_uchar1, fc_ulen1,
									  fc_uchar2, fc_ulen2);

				pfree(fc_uchar1);
				pfree(fc_uchar2);
			}
#else							/* 不使用 USE_ICU */
			/* 不应该发生 */
			elog(ERROR, "unsupported collprovider: %c", fc_sss->locale->provider);
#endif							/* 不使用 USE_ICU */
		}
		else
		{
#ifdef HAVE_LOCALE_T
			fc_result = strcoll_l(fc_sss->buf1, fc_sss->buf2, fc_sss->locale->info.lt);
#else
			/* 不应该发生 */
			elog(ERROR, "unsupported collprovider: %c", fc_sss->locale->provider);
#endif
		}
	}
	else
		fc_result = strcoll(fc_sss->buf1, fc_sss->buf2);

	/* 如有必要，打破平局。 */
	if (fc_result == 0 &&
		(!fc_sss->locale || fc_sss->locale->deterministic))
		fc_result = strcmp(fc_sss->buf1, fc_sss->buf2);

	/* 缓存结果，也许可以节省下次昂贵的 strcoll() 调用 */
	fc_sss->cache_blob = false;
	fc_sss->last_returned = fc_result;
	return fc_result;
}

/*
 * 排序支持的转换例程。将原始值转换为缩略键表示。我们的编码策略很简单——
 * 将 strxfrm() 数据块的前 8 字节打包到 Datum 中（在小端机器上，这 8 字节
 * 以相反的顺序存储），并将其视为无符号整数。当使用 "C" 
 * 区域设置时，或者在 bytea 的情况下，仅从原始值使用 memcpy()。
 */
static Datum fc_varstr_abbrev_convert(Datum fc_original, SortSupport fc_ssup)
{
	VarStringSortSupport *fc_sss = (VarStringSortSupport *) fc_ssup->ssup_extra;
	VarString  *fc_authoritative = DatumGetVarStringPP(fc_original);
	char	   *fc_authoritative_data = VARDATA_ANY(fc_authoritative);

	/* 工作状态 */
	Datum		fc_res;
	char	   *fc_pres;
	int			fc_len;
	uint32		fc_hash;

	fc_pres = (char *) &fc_res;
	/* memset()，所以任何未被覆盖的字节都是 NUL */
	memset(fc_pres, 0, sizeof(Datum));
	fc_len = VARSIZE_ANY_EXHDR(fc_authoritative);

	/* 获取字节数，忽略尾随空格 */
	if (fc_sss->typid == BPCHAROID)
		fc_len = bpchartruelen(fc_authoritative_data, fc_len);

	/*
	 * 如果我们使用 C 排序规则，使用 memcpy() 而不是 strxfrm() 来
	 * 缩略键。C 区域设置的完整比较器始终是
	 * memcmp()。允许 bytea 调用者（始终强制使用 C 排序规则的调用者——bytea 不是
	 * 可排序类型，但这种方法很方便）使用 strxfrm() 是不正确的。这是因为 bytea
	 * 字符串可能包含 NUL 字节。此外，这样应该更快。
	 *
	 * 更一般地说，bytea 调用者在字符串中可以包含 NUL 字节是可以的，因为
	 * 缩略的 cmp 不需要区分终止的 NUL 字节与
	 * 在权威表示中表示实际 NUL 的 NUL 字节。希望在一个
	 * 缩略键的终止 NUL 字节处或之后的比较将直接解决该比较
	 * 而无需查询权威表示；具体来说，较长字符串中的某个后续非 NUL 字节可以
	 * 在较短字符串后的后续终止 NUL 处解析比较。在那里通常会有一种
	 * 实际上是“按长度”解析的情况。
	 *
	 * 如果这没有奏效——如果较长字符串中在较短字符串（第一个）
	 * 终止 NUL 的偏移位置处或之后的所有字节实际上都代表权威二进制字符串中的
	 * NUL 字节（或许在较长字符串的末尾有一些*终止* NUL 字节，假如它仍然很小）
	 * ——那么将发生权威的平局打破，做出正确的事情：明确考虑字符串长度。
	 */
	if (fc_sss->collate_c)
		memcpy(fc_pres, fc_authoritative_data, Min(fc_len, sizeof(Datum)));
	else
	{
		Size		fc_bsize;
#ifdef USE_ICU
		int32_t		fc_ulen = -1;
		UChar	   *fc_uchar = NULL;
#endif

		/*
		 * 我们没有使用 C 排序规则，因此退回到 strxfrm 或 ICU
		 * 类似物。
		 */

		/* 按惯例，我们使用缓冲区1来存储并以NUL结尾 */
		if (fc_len >= fc_sss->buflen1)
		{
			fc_sss->buflen1 = Max(fc_len + 1, Min(fc_sss->buflen1 * 2, MaxAllocSize));
			fc_sss->buf1 = repalloc(fc_sss->buf1, fc_sss->buflen1);
		}

		/* 可能可以重用上一个调用的strxfrm() blob */
		if (fc_sss->last_len1 == fc_len && fc_sss->cache_blob &&
			memcmp(fc_sss->buf1, fc_authoritative_data, fc_len) == 0)
		{
			memcpy(fc_pres, fc_sss->buf2, Min(sizeof(Datum), fc_sss->last_len2));
			/* 没有影响基数的更改，因此不需要哈希 */
			goto done;
		}

		memcpy(fc_sss->buf1, fc_authoritative_data, fc_len);

		/*
		 * 就像strcoll()一样，strxfrm()期望一个以NUL结尾的字符串。对于ICU来说不是必需的，但没有坏处。
		 */
		fc_sss->buf1[fc_len] = '\0';
		fc_sss->last_len1 = fc_len;

#ifdef USE_ICU
		/* 当使用ICU且不是UTF8时，将字符串转换为UChar。 */
		if (fc_sss->locale && fc_sss->locale->provider == COLLPROVIDER_ICU &&
			GetDatabaseEncoding() != PG_UTF8)
			fc_ulen = icu_to_uchar(&fc_uchar, fc_sss->buf1, fc_len);
#endif

		/*
		 * 循环：调用strxfrm()或ucol_getSortKey()，可能会增加缓冲区，
		 * 然后再试一次。如果结果不合适，这两个函数的结果缓冲区
		 * 内容是未定义的，因此我们需要重试
		 * 直到一切都合适，尽管我们最终只需要最前面的几个字节。
		 * 然而，当使用ucol_nextSortKeyPart()时，我们只要求
		 * 实际需要的字节数。
		 */
		for (;;)
		{
#ifdef USE_ICU
			if (fc_sss->locale && fc_sss->locale->provider == COLLPROVIDER_ICU)
			{
				/*
				 * 当使用UTF8时，使用迭代接口，因此我们只
				 * 需要生成实际所需的字节数。
				 */
				if (GetDatabaseEncoding() == PG_UTF8)
				{
					UCharIterator fc_iter;
					uint32_t	fc_state[2];
					UErrorCode	fc_status;

					uiter_setUTF8(&fc_iter, fc_sss->buf1, fc_len);
					fc_state[0] = fc_state[1] = 0;	/* 不会再需要那个 */
					fc_status = U_ZERO_ERROR;
					fc_bsize = ucol_nextSortKeyPart(fc_sss->locale->info.icu.ucol,
												 &fc_iter,
												 fc_state,
												 (uint8_t *) fc_sss->buf2,
												 Min(sizeof(Datum), fc_sss->buflen2),
												 &fc_status);
					if (U_FAILURE(fc_status))
						ereport(ERROR,
								(errmsg("sort key generation failed: %s",
										u_errorName(fc_status))));
				}
				else
					fc_bsize = ucol_getSortKey(fc_sss->locale->info.icu.ucol,
											fc_uchar, fc_ulen,
											(uint8_t *) fc_sss->buf2, fc_sss->buflen2);
			}
			else
#endif
#ifdef HAVE_LOCALE_T
			if (fc_sss->locale && fc_sss->locale->provider == COLLPROVIDER_LIBC)
				fc_bsize = strxfrm_l(fc_sss->buf2, fc_sss->buf1,
								  fc_sss->buflen2, fc_sss->locale->info.lt);
			else
#endif
				fc_bsize = strxfrm(fc_sss->buf2, fc_sss->buf1, fc_sss->buflen2);

			fc_sss->last_len2 = fc_bsize;
			if (fc_bsize < fc_sss->buflen2)
				break;

			/*
			 * 增加缓冲区并重试。
			 */
			fc_sss->buflen2 = Max(fc_bsize + 1,
							   Min(fc_sss->buflen2 * 2, MaxAllocSize));
			fc_sss->buf2 = repalloc(fc_sss->buf2, fc_sss->buflen2);
		}

		/*
		 * 每个Datum字节始终被比较。这是安全的，因为
		 * strxfrm() blob本身是以NUL结尾的，不会有
		 * 误解任何不打算被解释为
		 * 逻辑表示终止的NUL字节的危险。
		 *
		 * （实际上，即使blob中有NUL字节也是可以的。参见上面的bytea案例备注。）
		 */
		memcpy(fc_pres, fc_sss->buf2, Min(sizeof(Datum), fc_bsize));

#ifdef USE_ICU
		if (fc_uchar)
			pfree(fc_uchar);
#endif
	}

	/*
	 * 使用HyperLogLog维持缩写键和原始
	 * 权威键的近似基数。在最坏情况下用于便宜的保险，
	 * 在这种情况下我们对许多字符串转换没有节省
	 * 在基于strcoll()的比较中。varstr_abbrev_abort()使用这些统计信息。
	 *
	 * 首先，适当哈希键，或其显著部分。混入长度
	 * 以补偿在差异超过
	 * PG_CACHE_LINE_SIZE字节的情况下，从而限制哈希的开销。
	 */
	fc_hash = DatumGetUInt32(hash_any((unsigned char *) fc_authoritative_data,
								   Min(fc_len, PG_CACHE_LINE_SIZE)));

	if (fc_len > PG_CACHE_LINE_SIZE)
		fc_hash ^= DatumGetUInt32(hash_uint32((uint32) fc_len));

	addHyperLogLog(&fc_sss->full_card, fc_hash);

	/* 哈希简写键 */
#if SIZEOF_DATUM == 8
	{
		uint32		fc_lohalf,
					fc_hihalf;

		fc_lohalf = (uint32) fc_res;
		fc_hihalf = (uint32) (fc_res >> 32);
		fc_hash = DatumGetUInt32(hash_uint32(fc_lohalf ^ fc_hihalf));
	}
#else							/* SIZEOF_DATUM != 8 */
	hash = DatumGetUInt32(hash_uint32((uint32) res));
#endif

	addHyperLogLog(&fc_sss->abbr_card, fc_hash);

	/* 缓存结果，也许下次可以节省一个昂贵的strxfrm()调用 */
	fc_sss->cache_blob = true;
done:

	 /*
	 * 在小端机器上进行字节交换。
	 *
	 * 这对于 ssup_datum_unsigned_cmp()（无符号整数
	 * 3 路比较器）在所有平台上正常工作是必要的。如果我们不这样做，
	 * 比较器将不得不调用 memcmp()，使用指向每个缩写键
	 * 第一个字节的一对指针，这样速度较慢。
	 */
	fc_res = DatumBigEndianToNative(fc_res);

	/* 不要在这里泄漏内存 */
	if (PointerGetDatum(fc_authoritative) != fc_original)
		pfree(fc_authoritative);

	return fc_res;
}

/*
 * 用于估计缩写键优化效果的回调，使用
 * 启发式规则。返回值指示是否应该中止缩写优化，
 * 根据其预测的有效性。
 */
static bool fc_varstr_abbrev_abort(int fc_memtupcount, SortSupport fc_ssup)
{
	VarStringSortSupport *fc_sss = (VarStringSortSupport *) fc_ssup->ssup_extra;
	double		fc_abbrev_distinct,
				fc_key_distinct;

	Assert(fc_ssup->abbreviate);

	/* 稍安勿躁 */
	if (fc_memtupcount < 100)
		return false;

	fc_abbrev_distinct = estimateHyperLogLog(&fc_sss->abbr_card);
	fc_key_distinct = estimateHyperLogLog(&fc_sss->full_card);

	/*
	 * 将基数估计压缩到至少一个不同的值。虽然
	 * NULL通常被忽略，但如果迄今为止只观察到NULL值，
	 * 那可能会错误地表示成本，如果我们未能进行压缩。
	 */
	if (fc_abbrev_distinct <= 1.0)
		fc_abbrev_distinct = 1.0;

	if (fc_key_distinct <= 1.0)
		fc_key_distinct = 1.0;

	/*
	 * 在最坏的情况下，所有缩写键都是相同的，而与此同时，
	 * 在缩写中未捕捉到完整键字符串中的差异。
	 */
#ifdef TRACE_SORT
	if (trace_sort)
	{
		double		fc_norm_abbrev_card = fc_abbrev_distinct / (double) fc_memtupcount;

		elog(LOG, "varstr_abbrev: abbrev_distinct after %d: %f "
			 "(key_distinct: %f, norm_abbrev_card: %f, prop_card: %f)",
			 fc_memtupcount, fc_abbrev_distinct, fc_key_distinct, fc_norm_abbrev_card,
			 fc_sss->prop_card);
	}
#endif

	/*
	 * 如果不同的缩写键的数量大致与
	 * 不同的权威原始键的数量匹配，那就是继续的充分理由。
	 * 如果大多数平局打破者仅使用memcmp()，我们即使在
	 * 具有非常低基数的集合中也可以获胜。这是到目前为止最重要的
	 * 考虑因素。
	 *
	 * 尽管在缩写键级别解决的比较
	 * 要比使用memcmp()解决的平局打破者便宜得多，但这两种结果
	 * 都比一旦开始排序的完整strcoll()便宜得多，因此
	 * 在评估缩写基数与集合的整体大小以更
	 * 准确地建模成本时，这似乎没有必要。假设缩写比较和
	 * 通过便宜的基于memcmp()的权威解决的缩写比较是等效的。
	 */
	if (fc_abbrev_distinct > fc_key_distinct * fc_sss->prop_card)
	{
		
/*
		 * 当我们超过10,000个元组时，要求下一次调用时强烈衰减所需基数。
		 *
		 * 这很有用，因为平均所需的比较次数以对数线性速度增加，且在大约10,000个元组时，这个因素将开始主导字符串转换的线性成本（这是一个保守的估计）。衰减率选择得比二分法稍微保守一些——因为我们是在memtupcount加倍时被调用的——这样，成本模型在衰减后的第一次调用中不会实际中止。这个衰减率主要是为了防止缩写基数跟踪完整键基数的准确度突然发生剧烈波动。衰减还可以防止边际情况被过晚中止，当字符串转换的投入过多时就是这种情况。
		 *
		 * 对于几百万个不同字符串的集合，只有几万个不同的缩写键仍然可以获得显著的好处。这通常发生在每个缩写键大致代理集合全键的均匀数量时。如果不是这样，我们希望能够尽早捕获并中止。如果没有尽早捕获，到问题显现时可能就不值得中止了。
		 */
		if (fc_memtupcount > 10000)
			fc_sss->prop_card *= 0.65;

		return false;
	}

	/*
	 * 中止缩写策略。
	 *
	 * 最坏的情况是，所有缩写键相同，而所有原始字符串不同，通常只会看到小到中等大小字符串列表的执行时间约有10%的回归。而在现代CPU中，当缓存停滞是主要成本时，我们通常可以期待非常大的改进，特别是对于具有适度到较高缩写基数的字符串集合。损失不大，但收获颇丰，这反映了我们的策略。
	 */
#ifdef TRACE_SORT
	if (trace_sort)
		elog(LOG, "varstr_abbrev: aborted abbreviation at %d "
			 "(abbrev_distinct: %f, key_distinct: %f, prop_card: %f)",
			 fc_memtupcount, fc_abbrev_distinct, fc_key_distinct, fc_sss->prop_card);
#endif

	return true;
}

/*
 * 字符类型运算符类的通用equalimage支持函数。
 * 禁用与非确定性排序的重复数据删除的使用。
 */
Datum btvarstrequalimage(PG_FUNCTION_ARGS)
{
	/* Oid		opcintype = PG_GETARG_OID(0); */
	Oid			fc_collid = PG_GET_COLLATION();

	fc_check_collation_set(fc_collid);

	if (lc_collate_is_c(fc_collid) ||
		fc_collid == DEFAULT_COLLATION_OID ||
		get_collation_isdeterministic(fc_collid))
		PG_RETURN_BOOL(true);
	else
		PG_RETURN_BOOL(false);
}

Datum text_larger(PG_FUNCTION_ARGS)
{
	text	   *fc_arg1 = PG_GETARG_TEXT_PP(0);
	text	   *fc_arg2 = PG_GETARG_TEXT_PP(1);
	text	   *fc_result;

	fc_result = ((fc_text_cmp(fc_arg1, fc_arg2, PG_GET_COLLATION()) > 0) ? fc_arg1 : fc_arg2);

	PG_RETURN_TEXT_P(fc_result);
}

Datum text_smaller(PG_FUNCTION_ARGS)
{
	text	   *fc_arg1 = PG_GETARG_TEXT_PP(0);
	text	   *fc_arg2 = PG_GETARG_TEXT_PP(1);
	text	   *fc_result;

	fc_result = ((fc_text_cmp(fc_arg1, fc_arg2, PG_GET_COLLATION()) < 0) ? fc_arg1 : fc_arg2);

	PG_RETURN_TEXT_P(fc_result);
}


/*
 * 文本类型和名称类型的跨类型比较函数。
 */

Datum nameeqtext(PG_FUNCTION_ARGS)
{
	Name		fc_arg1 = PG_GETARG_NAME(0);
	text	   *fc_arg2 = PG_GETARG_TEXT_PP(1);
	size_t		fc_len1 = strlen(NameStr(*fc_arg1));
	size_t		fc_len2 = VARSIZE_ANY_EXHDR(fc_arg2);
	Oid			fc_collid = PG_GET_COLLATION();
	bool		fc_result;

	fc_check_collation_set(fc_collid);

	if (fc_collid == C_COLLATION_OID)
		fc_result = (fc_len1 == fc_len2 &&
				  memcmp(NameStr(*fc_arg1), VARDATA_ANY(fc_arg2), fc_len1) == 0);
	else
		fc_result = (varstr_cmp(NameStr(*fc_arg1), fc_len1,
							 VARDATA_ANY(fc_arg2), fc_len2,
							 fc_collid) == 0);

	PG_FREE_IF_COPY(fc_arg2, 1);

	PG_RETURN_BOOL(fc_result);
}

Datum texteqname(PG_FUNCTION_ARGS)
{
	text	   *fc_arg1 = PG_GETARG_TEXT_PP(0);
	Name		fc_arg2 = PG_GETARG_NAME(1);
	size_t		fc_len1 = VARSIZE_ANY_EXHDR(fc_arg1);
	size_t		fc_len2 = strlen(NameStr(*fc_arg2));
	Oid			fc_collid = PG_GET_COLLATION();
	bool		fc_result;

	fc_check_collation_set(fc_collid);

	if (fc_collid == C_COLLATION_OID)
		fc_result = (fc_len1 == fc_len2 &&
				  memcmp(VARDATA_ANY(fc_arg1), NameStr(*fc_arg2), fc_len1) == 0);
	else
		fc_result = (varstr_cmp(VARDATA_ANY(fc_arg1), fc_len1,
							 NameStr(*fc_arg2), fc_len2,
							 fc_collid) == 0);

	PG_FREE_IF_COPY(fc_arg1, 0);

	PG_RETURN_BOOL(fc_result);
}

Datum namenetext(PG_FUNCTION_ARGS)
{
	Name		fc_arg1 = PG_GETARG_NAME(0);
	text	   *fc_arg2 = PG_GETARG_TEXT_PP(1);
	size_t		fc_len1 = strlen(NameStr(*fc_arg1));
	size_t		fc_len2 = VARSIZE_ANY_EXHDR(fc_arg2);
	Oid			fc_collid = PG_GET_COLLATION();
	bool		fc_result;

	fc_check_collation_set(fc_collid);

	if (fc_collid == C_COLLATION_OID)
		fc_result = !(fc_len1 == fc_len2 &&
				   memcmp(NameStr(*fc_arg1), VARDATA_ANY(fc_arg2), fc_len1) == 0);
	else
		fc_result = !(varstr_cmp(NameStr(*fc_arg1), fc_len1,
							  VARDATA_ANY(fc_arg2), fc_len2,
							  fc_collid) == 0);

	PG_FREE_IF_COPY(fc_arg2, 1);

	PG_RETURN_BOOL(fc_result);
}

Datum textnename(PG_FUNCTION_ARGS)
{
	text	   *fc_arg1 = PG_GETARG_TEXT_PP(0);
	Name		fc_arg2 = PG_GETARG_NAME(1);
	size_t		fc_len1 = VARSIZE_ANY_EXHDR(fc_arg1);
	size_t		fc_len2 = strlen(NameStr(*fc_arg2));
	Oid			fc_collid = PG_GET_COLLATION();
	bool		fc_result;

	fc_check_collation_set(fc_collid);

	if (fc_collid == C_COLLATION_OID)
		fc_result = !(fc_len1 == fc_len2 &&
				   memcmp(VARDATA_ANY(fc_arg1), NameStr(*fc_arg2), fc_len1) == 0);
	else
		fc_result = !(varstr_cmp(VARDATA_ANY(fc_arg1), fc_len1,
							  NameStr(*fc_arg2), fc_len2,
							  fc_collid) == 0);

	PG_FREE_IF_COPY(fc_arg1, 0);

	PG_RETURN_BOOL(fc_result);
}

Datum btnametextcmp(PG_FUNCTION_ARGS)
{
	Name		fc_arg1 = PG_GETARG_NAME(0);
	text	   *fc_arg2 = PG_GETARG_TEXT_PP(1);
	int32		fc_result;

	fc_result = varstr_cmp(NameStr(*fc_arg1), strlen(NameStr(*fc_arg1)),
						VARDATA_ANY(fc_arg2), VARSIZE_ANY_EXHDR(fc_arg2),
						PG_GET_COLLATION());

	PG_FREE_IF_COPY(fc_arg2, 1);

	PG_RETURN_INT32(fc_result);
}

Datum bttextnamecmp(PG_FUNCTION_ARGS)
{
	text	   *fc_arg1 = PG_GETARG_TEXT_PP(0);
	Name		fc_arg2 = PG_GETARG_NAME(1);
	int32		fc_result;

	fc_result = varstr_cmp(VARDATA_ANY(fc_arg1), VARSIZE_ANY_EXHDR(fc_arg1),
						NameStr(*fc_arg2), strlen(NameStr(*fc_arg2)),
						PG_GET_COLLATION());

	PG_FREE_IF_COPY(fc_arg1, 0);

	PG_RETURN_INT32(fc_result);
}

#define CmpCall(cmpfunc) \
	DatumGetInt32(DirectFunctionCall2Coll(cmpfunc, \
										  PG_GET_COLLATION(), \
										  PG_GETARG_DATUM(0), \
										  PG_GETARG_DATUM(1)))

Datum namelttext(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(CmpCall(btnametextcmp) < 0);
}

Datum nameletext(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(CmpCall(btnametextcmp) <= 0);
}

Datum namegttext(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(CmpCall(btnametextcmp) > 0);
}

Datum namegetext(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(CmpCall(btnametextcmp) >= 0);
}

Datum textltname(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(CmpCall(bttextnamecmp) < 0);
}

Datum textlename(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(CmpCall(bttextnamecmp) <= 0);
}

Datum textgtname(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(CmpCall(bttextnamecmp) > 0);
}

Datum textgename(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(CmpCall(bttextnamecmp) >= 0);
}

#undef CmpCall


/*
 * 以下运算符支持文本数据项的逐字符比较，以允许构建适用于LIKE子句的索引。
 * 请注意，常规的texteq/textne比较运算符，以及常规支持函数1和2与"C"排序被假定与这些兼容！
 */

static int fc_internal_text_pattern_compare(text *fc_arg1, text *fc_arg2)
{
	int			fc_result;
	int			fc_len1,
				fc_len2;

	fc_len1 = VARSIZE_ANY_EXHDR(fc_arg1);
	fc_len2 = VARSIZE_ANY_EXHDR(fc_arg2);

	fc_result = memcmp(VARDATA_ANY(fc_arg1), VARDATA_ANY(fc_arg2), Min(fc_len1, fc_len2));
	if (fc_result != 0)
		return fc_result;
	else if (fc_len1 < fc_len2)
		return -1;
	else if (fc_len1 > fc_len2)
		return 1;
	else
		return 0;
}


Datum text_pattern_lt(PG_FUNCTION_ARGS)
{
	text	   *fc_arg1 = PG_GETARG_TEXT_PP(0);
	text	   *fc_arg2 = PG_GETARG_TEXT_PP(1);
	int			fc_result;

	fc_result = fc_internal_text_pattern_compare(fc_arg1, fc_arg2);

	PG_FREE_IF_COPY(fc_arg1, 0);
	PG_FREE_IF_COPY(fc_arg2, 1);

	PG_RETURN_BOOL(fc_result < 0);
}


Datum text_pattern_le(PG_FUNCTION_ARGS)
{
	text	   *fc_arg1 = PG_GETARG_TEXT_PP(0);
	text	   *fc_arg2 = PG_GETARG_TEXT_PP(1);
	int			fc_result;

	fc_result = fc_internal_text_pattern_compare(fc_arg1, fc_arg2);

	PG_FREE_IF_COPY(fc_arg1, 0);
	PG_FREE_IF_COPY(fc_arg2, 1);

	PG_RETURN_BOOL(fc_result <= 0);
}


Datum text_pattern_ge(PG_FUNCTION_ARGS)
{
	text	   *fc_arg1 = PG_GETARG_TEXT_PP(0);
	text	   *fc_arg2 = PG_GETARG_TEXT_PP(1);
	int			fc_result;

	fc_result = fc_internal_text_pattern_compare(fc_arg1, fc_arg2);

	PG_FREE_IF_COPY(fc_arg1, 0);
	PG_FREE_IF_COPY(fc_arg2, 1);

	PG_RETURN_BOOL(fc_result >= 0);
}


Datum text_pattern_gt(PG_FUNCTION_ARGS)
{
	text	   *fc_arg1 = PG_GETARG_TEXT_PP(0);
	text	   *fc_arg2 = PG_GETARG_TEXT_PP(1);
	int			fc_result;

	fc_result = fc_internal_text_pattern_compare(fc_arg1, fc_arg2);

	PG_FREE_IF_COPY(fc_arg1, 0);
	PG_FREE_IF_COPY(fc_arg2, 1);

	PG_RETURN_BOOL(fc_result > 0);
}


Datum bttext_pattern_cmp(PG_FUNCTION_ARGS)
{
	text	   *fc_arg1 = PG_GETARG_TEXT_PP(0);
	text	   *fc_arg2 = PG_GETARG_TEXT_PP(1);
	int			fc_result;

	fc_result = fc_internal_text_pattern_compare(fc_arg1, fc_arg2);

	PG_FREE_IF_COPY(fc_arg1, 0);
	PG_FREE_IF_COPY(fc_arg2, 1);

	PG_RETURN_INT32(fc_result);
}


Datum bttext_pattern_sortsupport(PG_FUNCTION_ARGS)
{
	SortSupport fc_ssup = (SortSupport) PG_GETARG_POINTER(0);
	MemoryContext fc_oldcontext;

	fc_oldcontext = MemoryContextSwitchTo(fc_ssup->ssup_cxt);

	/* 使用通用字符串 SortSupport，强制使用 "C" 排序 */
	varstr_sortsupport(fc_ssup, TEXTOID, C_COLLATION_OID);

	MemoryContextSwitchTo(fc_oldcontext);

	PG_RETURN_VOID();
}


/*-------------------------------------------------------------
 * byteaoctetlen
 *
 * 获取类型'bytea'实例中包含的字节数
 *-------------------------------------------------------------
 */
Datum byteaoctetlen(PG_FUNCTION_ARGS)
{
	Datum		fc_str = PG_GETARG_DATUM(0);

	/* 我们根本无需解压输入 */
	PG_RETURN_INT32(toast_raw_datum_size(fc_str) - VARHDRSZ);
}

/*
 * byteacat -
 *	  接受两个bytea*并返回一个bytea*，这是这两个的连接。
 *
 * 从textcat克隆并根据需要进行了修改。
 */
Datum byteacat(PG_FUNCTION_ARGS)
{
	bytea	   *fc_t1 = PG_GETARG_BYTEA_PP(0);
	bytea	   *fc_t2 = PG_GETARG_BYTEA_PP(1);

	PG_RETURN_BYTEA_P(fc_bytea_catenate(fc_t1, fc_t2));
}

/*
 * bytea_catenate
 *	byteacat()的核心内容，分离出来以便其它函数使用
 *
 * 参数可以采用短头格式，但不得压缩或脱线
 */
static bytea * fc_bytea_catenate(bytea *fc_t1, bytea *fc_t2)
{
	bytea	   *fc_result;
	int			fc_len1,
				fc_len2,
				fc_len;
	char	   *fc_ptr;

	fc_len1 = VARSIZE_ANY_EXHDR(fc_t1);
	fc_len2 = VARSIZE_ANY_EXHDR(fc_t2);

	/* 偏执...可能应该抛出错误？ */
	if (fc_len1 < 0)
		fc_len1 = 0;
	if (fc_len2 < 0)
		fc_len2 = 0;

	fc_len = fc_len1 + fc_len2 + VARHDRSZ;
	fc_result = (bytea *) palloc(fc_len);

	/* 设置结果字符串的大小... */
	SET_VARSIZE(fc_result, fc_len);

	/* 填充结果字符串的数据字段... */
	fc_ptr = VARDATA(fc_result);
	if (fc_len1 > 0)
		memcpy(fc_ptr, VARDATA_ANY(fc_t1), fc_len1);
	if (fc_len2 > 0)
		memcpy(fc_ptr + fc_len1, VARDATA_ANY(fc_t2), fc_len2);

	return fc_result;
}

#define PG_STR_GET_BYTEA(str_) \
	DatumGetByteaPP(DirectFunctionCall1(byteain, CStringGetDatum(str_)))

/*
 * bytea_substr()
 * 返回从指定位置开始的子字符串。
 * 从text_substr克隆并根据需要进行了修改。
 *
 * 输入：
 *	- 字符串
 *	- 起始位置（以1为基数）
 *	- 字符串长度（可选）
 *
 * 如果起始位置为零或更小，则从字符串的开始返回，长度要与SQL中的“负起始”一致。
 * 如果长度小于零，则抛出ERROR。如果未提供第三个参数（长度），则假定长度到字符串末尾。
 */
Datum bytea_substr(PG_FUNCTION_ARGS)
{
	PG_RETURN_BYTEA_P(fc_bytea_substring(PG_GETARG_DATUM(0),
									  PG_GETARG_INT32(1),
									  PG_GETARG_INT32(2),
									  false));
}

/*
 * bytea_substr_no_len -
 *	  包装器以避免因为一个函数接受不同数量的参数而导致opr_sanity失败。
 */
Datum bytea_substr_no_len(PG_FUNCTION_ARGS)
{
	PG_RETURN_BYTEA_P(fc_bytea_substring(PG_GETARG_DATUM(0),
									  PG_GETARG_INT32(1),
									  -1,
									  true));
}

static bytea * fc_bytea_substring(Datum fc_str,
				int fc_S,
				int fc_L,
				bool fc_length_not_specified)
{
	int32		fc_S1;				/* 调整后的起始位置 */
	int32		fc_L1;				/* 调整后的子字符串长度 */
	int32		fc_E;				/* 结束位置 */

	/*
	 * 此处的逻辑一般应与text_substring()匹配。
	 */
	fc_S1 = Max(fc_S, 1);

	if (fc_length_not_specified)
	{
		/*
		 * 未传递长度 - DatumGetByteaPSlice() 如果我们传递负值作为长度，则会抓取整个字符串直到末尾。
		 */
		fc_L1 = -1;
	}
	else if (fc_L < 0)
	{
		/* SQL99 规定对于 E < S，即负长度抛出错误 */
		ereport(ERROR,
				(errcode(ERRCODE_SUBSTRING_ERROR),
				 errmsg("negative substring length not allowed")));
		fc_L1 = -1;				/* 消除愚蠢编译器的警告 */
	}
	else if (pg_add_s32_overflow(fc_S, fc_L, &fc_E))
	{
		/*
		 * L 可能大到足以使 S + L 溢出，在这种情况下
		 * 子字符串必须位于字符串的结尾。
		 */
		fc_L1 = -1;
	}
	else
	{
		/*
		 * 结束位置为零或负数的情况可能发生在起始为负数或一的情况下。SQL99规定返回一个零长度的字符串。
		 */
		if (fc_E < 1)
			return PG_STR_GET_BYTEA("");

		fc_L1 = fc_E - fc_S1;
	}

	/*
	 * 如果起始位置超过字符串的末尾，SQL99规定返回一个零长度的字符串 -- DatumGetByteaPSlice() 将为我们做到这一点。我们只需将S1转换为零基的起始位置。
	 */
	return DatumGetByteaPSlice(fc_str, fc_S1 - 1, fc_L1);
}

/*
 * byteaoverlay
 *	用第二个字符串替换第一个字符串的指定子字符串
 *
 * SQL标准通过子字符串和连接来定义OVERLAY()。此代码是对标准说明的直接实现。
 */
Datum byteaoverlay(PG_FUNCTION_ARGS)
{
	bytea	   *fc_t1 = PG_GETARG_BYTEA_PP(0);
	bytea	   *fc_t2 = PG_GETARG_BYTEA_PP(1);
	int			fc_sp = PG_GETARG_INT32(2);	/* 子字符串起始位置 */
	int			fc_sl = PG_GETARG_INT32(3);	/* 子字符串长度 */

	PG_RETURN_BYTEA_P(fc_bytea_overlay(fc_t1, fc_t2, fc_sp, fc_sl));
}

Datum byteaoverlay_no_len(PG_FUNCTION_ARGS)
{
	bytea	   *fc_t1 = PG_GETARG_BYTEA_PP(0);
	bytea	   *fc_t2 = PG_GETARG_BYTEA_PP(1);
	int			fc_sp = PG_GETARG_INT32(2);	/* 子字符串起始位置 */
	int			fc_sl;

	fc_sl = VARSIZE_ANY_EXHDR(fc_t2); /* 默认为长度(t2) */
	PG_RETURN_BYTEA_P(fc_bytea_overlay(fc_t1, fc_t2, fc_sp, fc_sl));
}

static bytea * fc_bytea_overlay(bytea *fc_t1, bytea *fc_t2, int fc_sp, int fc_sl)
{
	bytea	   *fc_result;
	bytea	   *fc_s1;
	bytea	   *fc_s2;
	int			fc_sp_pl_sl;

	/*
	 * 检查可能的整数溢出情况。对于负的 sp，抛出一个
	 * “子字符串长度”错误，因为这是根据规范对 OVERLAY() 的定义应当预期的。
	 */
	if (fc_sp <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_SUBSTRING_ERROR),
				 errmsg("negative substring length not allowed")));
	if (pg_add_s32_overflow(fc_sp, fc_sl, &fc_sp_pl_sl))
		ereport(ERROR,
				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
				 errmsg("integer out of range")));

	fc_s1 = fc_bytea_substring(PointerGetDatum(fc_t1), 1, fc_sp - 1, false);
	fc_s2 = fc_bytea_substring(PointerGetDatum(fc_t1), fc_sp_pl_sl, -1, true);
	fc_result = fc_bytea_catenate(fc_s1, fc_t2);
	fc_result = fc_bytea_catenate(fc_result, fc_s2);

	return fc_result;
}

/*
 * bit_count
 */
Datum bytea_bit_count(PG_FUNCTION_ARGS)
{
	bytea	   *fc_t1 = PG_GETARG_BYTEA_PP(0);

	PG_RETURN_INT64(pg_popcount(VARDATA_ANY(fc_t1), VARSIZE_ANY_EXHDR(fc_t1)));
}

/*
 * byteapos -
 *	  返回指定子字符串的位置。
 *	  实现SQL POSITION()函数。
 * 从textpos克隆并根据需要修改。
 */
Datum byteapos(PG_FUNCTION_ARGS)
{
	bytea	   *fc_t1 = PG_GETARG_BYTEA_PP(0);
	bytea	   *fc_t2 = PG_GETARG_BYTEA_PP(1);
	int			fc_pos;
	int			fc_px,
				fc_p;
	int			fc_len1,
				fc_len2;
	char	   *fc_p1,
			   *fc_p2;

	fc_len1 = VARSIZE_ANY_EXHDR(fc_t1);
	fc_len2 = VARSIZE_ANY_EXHDR(fc_t2);

	if (fc_len2 <= 0)
		PG_RETURN_INT32(1);		/* 空模式的结果 */

	fc_p1 = VARDATA_ANY(fc_t1);
	fc_p2 = VARDATA_ANY(fc_t2);

	fc_pos = 0;
	fc_px = (fc_len1 - fc_len2);
	for (fc_p = 0; fc_p <= fc_px; fc_p++)
	{
		if ((*fc_p2 == *fc_p1) && (memcmp(fc_p1, fc_p2, fc_len2) == 0))
		{
			fc_pos = fc_p + 1;
			break;
		};
		fc_p1++;
	};

	PG_RETURN_INT32(fc_pos);
}

/*-------------------------------------------------------------
 * byteaGetByte
 *
 * 此例程将 "bytea" 视为字节数组。
 * 它返回第N个字节（介于0到255之间的数字）。
 *-------------------------------------------------------------
 */
Datum byteaGetByte(PG_FUNCTION_ARGS)
{
	bytea	   *fc_v = PG_GETARG_BYTEA_PP(0);
	int32		fc_n = PG_GETARG_INT32(1);
	int			fc_len;
	int			fc_byte;

	fc_len = VARSIZE_ANY_EXHDR(fc_v);

	if (fc_n < 0 || fc_n >= fc_len)
		ereport(ERROR,
				(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
				 errmsg("index %d out of valid range, 0..%d",
						fc_n, fc_len - 1)));

	fc_byte = ((unsigned char *) VARDATA_ANY(fc_v))[fc_n];

	PG_RETURN_INT32(fc_byte);
}

/*-------------------------------------------------------------
 * byteaGetBit
 *
 * 此例程将 "bytea" 类型视为比特数组。
 * 它返回第N个位的值（0或1）。
 *
 *-------------------------------------------------------------
 */
Datum byteaGetBit(PG_FUNCTION_ARGS)
{
	bytea	   *fc_v = PG_GETARG_BYTEA_PP(0);
	int64		fc_n = PG_GETARG_INT64(1);
	int			fc_byteNo,
				fc_bitNo;
	int			fc_len;
	int			fc_byte;

	fc_len = VARSIZE_ANY_EXHDR(fc_v);

	if (fc_n < 0 || fc_n >= (int64) fc_len * 8)
		ereport(ERROR,
				(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
				 errmsg("index %lld out of valid range, 0..%lld",
						(long long) fc_n, (long long) fc_len * 8 - 1)));

	/* n/8现在已知 < len，因此安全转换为int */
	fc_byteNo = (int) (fc_n / 8);
	fc_bitNo = (int) (fc_n % 8);

	fc_byte = ((unsigned char *) VARDATA_ANY(fc_v))[fc_byteNo];

	if (fc_byte & (1 << fc_bitNo))
		PG_RETURN_INT32(1);
	else
		PG_RETURN_INT32(0);
}

/*-------------------------------------------------------------
 * byteaSetByte
 *
 * 给定一个'bytea'类型的实例，创建一个新实例，
 * 第N个字节设置为给定值。
 *
 *-------------------------------------------------------------
 */
Datum byteaSetByte(PG_FUNCTION_ARGS)
{
	bytea	   *fc_res = PG_GETARG_BYTEA_P_COPY(0);
	int32		fc_n = PG_GETARG_INT32(1);
	int32		fc_newByte = PG_GETARG_INT32(2);
	int			fc_len;

	fc_len = VARSIZE(fc_res) - VARHDRSZ;

	if (fc_n < 0 || fc_n >= fc_len)
		ereport(ERROR,
				(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
				 errmsg("index %d out of valid range, 0..%d",
						fc_n, fc_len - 1)));

	/*
	 * 现在设置字节。
	 */
	((unsigned char *) VARDATA(fc_res))[fc_n] = fc_newByte;

	PG_RETURN_BYTEA_P(fc_res);
}

/*-------------------------------------------------------------
 * byteaSetBit
 *
 * 给定一个'bytea'类型的实例，创建一个新实例，
 * 第N个位设置为给定值。
 *
 *-------------------------------------------------------------
 */
Datum byteaSetBit(PG_FUNCTION_ARGS)
{
	bytea	   *fc_res = PG_GETARG_BYTEA_P_COPY(0);
	int64		fc_n = PG_GETARG_INT64(1);
	int32		fc_newBit = PG_GETARG_INT32(2);
	int			fc_len;
	int			fc_oldByte,
				fc_newByte;
	int			fc_byteNo,
				fc_bitNo;

	fc_len = VARSIZE(fc_res) - VARHDRSZ;

	if (fc_n < 0 || fc_n >= (int64) fc_len * 8)
		ereport(ERROR,
				(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
				 errmsg("index %lld out of valid range, 0..%lld",
						(long long) fc_n, (long long) fc_len * 8 - 1)));

	/* n/8现在已知 < len，因此安全转换为int */
	fc_byteNo = (int) (fc_n / 8);
	fc_bitNo = (int) (fc_n % 8);

	/*
	 * 安全检查！
	 */
	if (fc_newBit != 0 && fc_newBit != 1)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("new bit must be 0 or 1")));

	/*
	 * 更新字节。
	 */
	fc_oldByte = ((unsigned char *) VARDATA(fc_res))[fc_byteNo];

	if (fc_newBit == 0)
		fc_newByte = fc_oldByte & (~(1 << fc_bitNo));
	else
		fc_newByte = fc_oldByte | (1 << fc_bitNo);

	((unsigned char *) VARDATA(fc_res))[fc_byteNo] = fc_newByte;

	PG_RETURN_BYTEA_P(fc_res);
}


/* text_name()
 * 将文本类型转换为名称类型。
 */
Datum text_name(PG_FUNCTION_ARGS)
{
	text	   *fc_s = PG_GETARG_TEXT_PP(0);
	Name		fc_result;
	int			fc_len;

	fc_len = VARSIZE_ANY_EXHDR(fc_s);

	/* 截断超长输入 */
	if (fc_len >= NAMEDATALEN)
		fc_len = pg_mbcliplen(VARDATA_ANY(fc_s), fc_len, NAMEDATALEN - 1);

	/* 我们在这里使用 palloc0 以确保结果是零填充的 */
	fc_result = (Name) palloc0(NAMEDATALEN);
	memcpy(NameStr(*fc_result), VARDATA_ANY(fc_s), fc_len);

	PG_RETURN_NAME(fc_result);
}

/* name_text()
 * 将名称类型转换为文本类型。
 */
Datum name_text(PG_FUNCTION_ARGS)
{
	Name		fc_s = PG_GETARG_NAME(0);

	PG_RETURN_TEXT_P(cstring_to_text(NameStr(*fc_s)));
}


/*
 * textToQualifiedNameList - 将文本对象转换为名称列表
 *
 * 这实现了nextval()和其他传入文本参数表示合格名称的函数所需的输入解析。
 * 我们在点处拆分名称，如果没有双引号则小写，并且
 * 如果名称过长，则截断名称。
 */
List * textToQualifiedNameList(text *fc_textval)
{
	char	   *fc_rawname;
	List	   *fc_result = NIL;
	List	   *fc_namelist;
	ListCell   *fc_l;

	/* 转换为C字符串（处理可能的去霜冻）。 */
	/* 注意我们依赖于可以修改下面的rawname。 */
	fc_rawname = text_to_cstring(fc_textval);

	if (!SplitIdentifierString(fc_rawname, '.', &fc_namelist))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_NAME),
				 errmsg("invalid name syntax")));

	if (fc_namelist == NIL)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_NAME),
				 errmsg("invalid name syntax")));

	foreach(fc_l, fc_namelist)
	{
		char	   *fc_curname = (char *) lfirst(fc_l);

		fc_result = lappend(fc_result, makeString(pstrdup(fc_curname)));
	}

	pfree(fc_rawname);
	list_free(fc_namelist);

	return fc_result;
}

/*
 * SplitIdentifierString --- 解析包含标识符的字符串
 *
 * 这是textToQualifiedNameList的核心部分，并导出以供在
 * 其他情况中使用，例如解析GUC变量。在GUC情况下，
 * 避免内存泄漏很重要，因此该API旨在最大限度减少需要分配和释放的内容量。
 *
 * 输入：
 *	rawstring: 输入字符串；必须是可覆盖的！返回时，它已
 *			   被修改以包含分隔的标识符。
 *	separator: 期望的标识符之间的分隔标点（通常是'.'或','）。标识符周围也可能出现空格。
 * 输出：
 *	namelist: 填充了指向rawstring中标识符的palloc'd指针列表。调用者即使在错误返回时也应该对其进行list_free()。
 *
 * 如果一切正常则返回true，如果字符串中有语法错误则返回false。
 *
 * 注意，空字符串在这里被视为正常，但在textToQualifiedNameList中不被视为正常。
 */
bool SplitIdentifierString(char *fc_rawstring, char fc_separator,
					  List **fc_namelist)
{
	char	   *fc_nextp = fc_rawstring;
	bool		fc_done = false;

	*fc_namelist = NIL;

	while (scanner_isspace(*fc_nextp))
		fc_nextp++;				/* 跳过前导空白 */

	if (*fc_nextp == '\0')
		return true;			/* 允许空字符串 */

	/* 在循环的顶部，我们处于一个新标识符的开始。 */
	do
	{
		char	   *fc_curname;
		char	   *fc_endp;

		if (*fc_nextp == '"')
		{
			/* 引号名称 --- 合并引号对，不进行小写处理 */
			fc_curname = fc_nextp + 1;
			for (;;)
			{
				fc_endp = strchr(fc_nextp + 1, '"');
				if (fc_endp == NULL)
					return false;	/* 引号不匹配 */
				if (fc_endp[1] != '"')
					break;		/* 找到带引号名称的结束 */
				/* 将相邻的引号合并为一个引号，然后再次查看 */
				memmove(fc_endp, fc_endp + 1, strlen(fc_endp));
				fc_nextp = fc_endp;
			}
			/* endp 现在指向结束引号 */
			fc_nextp = fc_endp + 1;
		}
		else
		{
			/* 不带引号的名称 --- 扩展到分隔符或空白 */
			char	   *fc_downname;
			int			fc_len;

			fc_curname = fc_nextp;
			while (*fc_nextp && *fc_nextp != fc_separator &&
				   !scanner_isspace(*fc_nextp))
				fc_nextp++;
			fc_endp = fc_nextp;
			if (fc_curname == fc_nextp)
				return false;	/* 不允许空带引号的名称 */

			/*
			 * 小写标识符，使用与主词法分析器相同的代码。
			 *
			 * XXX 由于我们想在原地覆盖输入，因此不能支持增加字符串
			 * 长度的小写转换。鉴于当前实现的downcase_truncate_identifier，这并不是问题，
			 * 但我们可能会在某天对此采取一些措施。
			 */
			fc_len = fc_endp - fc_curname;
			fc_downname = downcase_truncate_identifier(fc_curname, fc_len, false);
			Assert(strlen(fc_downname) <= fc_len);
			strncpy(fc_curname, fc_downname, fc_len);	/* 这里要求使用strncpy */
			pfree(fc_downname);
		}

		while (scanner_isspace(*fc_nextp))
			fc_nextp++;			/* 跳过拖尾空白 */

		if (*fc_nextp == fc_separator)
		{
			fc_nextp++;
			while (scanner_isspace(*fc_nextp))
				fc_nextp++;		/* 跳过下一个的前导空白 */
			/* 我们期待另一个名称，因此 done 保持为 false */
		}
		else if (*fc_nextp == '\0')
			fc_done = true;
		else
			return false;		/* 语法无效 */

		/* 现在可以安全地用 null 重写分隔符 */
		*fc_endp = '\0';

		/* 如果名称超长则截断名称 */
		truncate_identifier(fc_curname, strlen(fc_curname), false);

		/*
		 * 完成隔离当前名称 --- 将其添加到列表
		 */
		*fc_namelist = lappend(*fc_namelist, fc_curname);

		/* 如果我们没有到达字符串末尾，则循环回来 */
	} while (!fc_done);

	return true;
}



/*
 * SplitDirectoriesString --- 解析包含文件/目录名称的字符串
 *
 * 此函数在文件名上也能正常工作；函数名称是历史遗留。
 *
 * 这类似于 SplitIdentifierString，除了解析规则旨在处理路径名而不是标识符：没有小写，允许嵌入空格，最大长度为 MAXPGPATH-1，并且我们对每个提取的字符串应用 canonicalize_path()。由于最后一点，返回的字符串是单独分配内存而不是指向 rawstring 的指针——但我们仍然会修改 rawstring。
 *
 * 输入：
 *	rawstring：输入字符串；必须是可修改的！
 *	separator：预期在目录之间的分隔符标点符号
 *			   （通常是 ',' 或 ';'）。目录周围也可以出现空格。
 * 输出：
 *	namelist：填充了 palloc 分配的目录名称列表。
 *			  调用者即使在错误返回时也应对其进行 list_free_deep()。
 *
 * 如果没问题则返回 true，如果字符串中存在语法错误则返回 false。
 *
 * 请注意，空字符串在这里被视为正常。
 */
bool SplitDirectoriesString(char *fc_rawstring, char fc_separator,
					   List **fc_namelist)
{
	char	   *fc_nextp = fc_rawstring;
	bool		fc_done = false;

	*fc_namelist = NIL;

	while (scanner_isspace(*fc_nextp))
		fc_nextp++;				/* 跳过前导空白 */

	if (*fc_nextp == '\0')
		return true;			/* 允许空字符串 */

	/* 在循环开始时，我们处于新目录的起始位置。 */
	do
	{
		char	   *fc_curname;
		char	   *fc_endp;

		if (*fc_nextp == '"')
		{
			/* 带引号的名称 --- 合并引号对 */
			fc_curname = fc_nextp + 1;
			for (;;)
			{
				fc_endp = strchr(fc_nextp + 1, '"');
				if (fc_endp == NULL)
					return false;	/* 引号不匹配 */
				if (fc_endp[1] != '"')
					break;		/* 找到带引号名称的结束 */
				/* 将相邻的引号合并为一个引号，然后再次查看 */
				memmove(fc_endp, fc_endp + 1, strlen(fc_endp));
				fc_nextp = fc_endp;
			}
			/* endp 现在指向结束引号 */
			fc_nextp = fc_endp + 1;
		}
		else
		{
			/* 未引号的名称 --- 延伸到分隔符或字符串末尾 */
			fc_curname = fc_endp = fc_nextp;
			while (*fc_nextp && *fc_nextp != fc_separator)
			{
				/* 不应包括尾随空格在名称中 */
				if (!scanner_isspace(*fc_nextp))
					fc_endp = fc_nextp + 1;
				fc_nextp++;
			}
			if (fc_curname == fc_endp)
				return false;	/* 不允许空带引号的名称 */
		}

		while (scanner_isspace(*fc_nextp))
			fc_nextp++;			/* 跳过拖尾空白 */

		if (*fc_nextp == fc_separator)
		{
			fc_nextp++;
			while (scanner_isspace(*fc_nextp))
				fc_nextp++;		/* 跳过下一个的前导空白 */
			/* 我们期待另一个名称，因此 done 保持为 false */
		}
		else if (*fc_nextp == '\0')
			fc_done = true;
		else
			return false;		/* 语法无效 */

		/* 现在可以安全地用 null 重写分隔符 */
		*fc_endp = '\0';

		/* 如果路径超长则截断路径 */
		if (strlen(fc_curname) >= MAXPGPATH)
			fc_curname[MAXPGPATH - 1] = '\0';

		/*
		 * 完成隔离当前名称 --- 将其添加到列表
		 */
		fc_curname = pstrdup(fc_curname);
		canonicalize_path(fc_curname);
		*fc_namelist = lappend(*fc_namelist, fc_curname);

		/* 如果我们没有到达字符串末尾，则循环回来 */
	} while (!fc_done);

	return true;
}


/*
 * SplitGUCList --- 解析包含标识符或文件名的字符串
 *
 * 此函数用于拆分 GUC_LIST_QUOTE GUC 变量的值，而不假设元素将作为标识符或文件名处理。
 * 我们假设输入已经通过 flatten_set_variable_args()，因此无需再进行小写（如果需要，这已经完成）。
 * 我们也不进行截断，因为我们不知道正确的最大长度。
 * 出于简单考虑，我们不允许嵌入空白（这不应该有问题，因为任何嵌入空格都应该导致双引号）。
 * 否则，该 API 与 SplitIdentifierString 相同。
 *
 * XXX 有这么多这段字符串分割逻辑的副本令人烦恼。
 * 然而，目前还不清楚拥有一个带有多个选项标志的函数会不会更好。
 *
 * XXX 在 src/bin/pg_dump/dumputils.c 中有此函数的一个版本。
 * 如果您必须更改此函数，请确保更新它。
 *
 * 输入：
 *	rawstring：输入字符串；必须是可覆盖的！返回时，它已被修改为包含分隔的标识符。
 *	separator：预期标识符之间的分隔符标点符号
 *			   （通常是 '.' 或 ','）。标识符周围也可以出现空格。
 * 输出：
 *	namelist：填充了指向 rawstring 中标识符的 palloc 列表的指针。调用者即使在错误返回时也应对其进行 list_free()。
 *
 * 如果没问题则返回 true，如果字符串中存在语法错误则返回 false。
 */
bool SplitGUCList(char *fc_rawstring, char fc_separator,
			 List **fc_namelist)
{
	char	   *fc_nextp = fc_rawstring;
	bool		fc_done = false;

	*fc_namelist = NIL;

	while (scanner_isspace(*fc_nextp))
		fc_nextp++;				/* 跳过前导空白 */

	if (*fc_nextp == '\0')
		return true;			/* 允许空字符串 */

	/* 在循环的顶部，我们处于一个新标识符的开始。 */
	do
	{
		char	   *fc_curname;
		char	   *fc_endp;

		if (*fc_nextp == '"')
		{
			/* 带引号的名称 --- 合并引号对 */
			fc_curname = fc_nextp + 1;
			for (;;)
			{
				fc_endp = strchr(fc_nextp + 1, '"');
				if (fc_endp == NULL)
					return false;	/* 引号不匹配 */
				if (fc_endp[1] != '"')
					break;		/* 找到带引号名称的结束 */
				/* 将相邻的引号合并为一个引号，然后再次查看 */
				memmove(fc_endp, fc_endp + 1, strlen(fc_endp));
				fc_nextp = fc_endp;
			}
			/* endp 现在指向结束引号 */
			fc_nextp = fc_endp + 1;
		}
		else
		{
			/* 不带引号的名称 --- 扩展到分隔符或空白 */
			fc_curname = fc_nextp;
			while (*fc_nextp && *fc_nextp != fc_separator &&
				   !scanner_isspace(*fc_nextp))
				fc_nextp++;
			fc_endp = fc_nextp;
			if (fc_curname == fc_nextp)
				return false;	/* 不允许空带引号的名称 */
		}

		while (scanner_isspace(*fc_nextp))
			fc_nextp++;			/* 跳过拖尾空白 */

		if (*fc_nextp == fc_separator)
		{
			fc_nextp++;
			while (scanner_isspace(*fc_nextp))
				fc_nextp++;		/* 跳过下一个的前导空白 */
			/* 我们期待另一个名称，因此 done 保持为 false */
		}
		else if (*fc_nextp == '\0')
			fc_done = true;
		else
			return false;		/* 语法无效 */

		/* 现在可以安全地用 null 重写分隔符 */
		*fc_endp = '\0';

		/*
		 * 完成隔离当前名称 --- 将其添加到列表
		 */
		*fc_namelist = lappend(*fc_namelist, fc_curname);

		/* 如果我们没有到达字符串末尾，则循环回来 */
	} while (!fc_done);

	return true;
}


/*****************************************************************************
 *	用于 bytea 的比较函数
 *
 * 注意：btree 索引需要这些例程避免内存泄漏；因此，务必小心释放工作副本的吐槽数据。大多数地方不需要如此小心。
 *****************************************************************************/

Datum byteaeq(PG_FUNCTION_ARGS)
{
	Datum		fc_arg1 = PG_GETARG_DATUM(0);
	Datum		fc_arg2 = PG_GETARG_DATUM(1);
	bool		fc_result;
	Size		fc_len1,
				fc_len2;

	/*
	 * 我们可以为不等长度使用快速路径，这可能会使我们
	 * 不必解压一个或两个值。
	 */
	fc_len1 = toast_raw_datum_size(fc_arg1);
	fc_len2 = toast_raw_datum_size(fc_arg2);
	if (fc_len1 != fc_len2)
		fc_result = false;
	else
	{
		bytea	   *fc_barg1 = DatumGetByteaPP(fc_arg1);
		bytea	   *fc_barg2 = DatumGetByteaPP(fc_arg2);

		fc_result = (memcmp(VARDATA_ANY(fc_barg1), VARDATA_ANY(fc_barg2),
						 fc_len1 - VARHDRSZ) == 0);

		PG_FREE_IF_COPY(fc_barg1, 0);
		PG_FREE_IF_COPY(fc_barg2, 1);
	}

	PG_RETURN_BOOL(fc_result);
}

Datum byteane(PG_FUNCTION_ARGS)
{
	Datum		fc_arg1 = PG_GETARG_DATUM(0);
	Datum		fc_arg2 = PG_GETARG_DATUM(1);
	bool		fc_result;
	Size		fc_len1,
				fc_len2;

	/*
	 * 我们可以为不等长度使用快速路径，这可能会使我们
	 * 不必解压一个或两个值。
	 */
	fc_len1 = toast_raw_datum_size(fc_arg1);
	fc_len2 = toast_raw_datum_size(fc_arg2);
	if (fc_len1 != fc_len2)
		fc_result = true;
	else
	{
		bytea	   *fc_barg1 = DatumGetByteaPP(fc_arg1);
		bytea	   *fc_barg2 = DatumGetByteaPP(fc_arg2);

		fc_result = (memcmp(VARDATA_ANY(fc_barg1), VARDATA_ANY(fc_barg2),
						 fc_len1 - VARHDRSZ) != 0);

		PG_FREE_IF_COPY(fc_barg1, 0);
		PG_FREE_IF_COPY(fc_barg2, 1);
	}

	PG_RETURN_BOOL(fc_result);
}

Datum bytealt(PG_FUNCTION_ARGS)
{
	bytea	   *fc_arg1 = PG_GETARG_BYTEA_PP(0);
	bytea	   *fc_arg2 = PG_GETARG_BYTEA_PP(1);
	int			fc_len1,
				fc_len2;
	int			fc_cmp;

	fc_len1 = VARSIZE_ANY_EXHDR(fc_arg1);
	fc_len2 = VARSIZE_ANY_EXHDR(fc_arg2);

	fc_cmp = memcmp(VARDATA_ANY(fc_arg1), VARDATA_ANY(fc_arg2), Min(fc_len1, fc_len2));

	PG_FREE_IF_COPY(fc_arg1, 0);
	PG_FREE_IF_COPY(fc_arg2, 1);

	PG_RETURN_BOOL((fc_cmp < 0) || ((fc_cmp == 0) && (fc_len1 < fc_len2)));
}

Datum byteale(PG_FUNCTION_ARGS)
{
	bytea	   *fc_arg1 = PG_GETARG_BYTEA_PP(0);
	bytea	   *fc_arg2 = PG_GETARG_BYTEA_PP(1);
	int			fc_len1,
				fc_len2;
	int			fc_cmp;

	fc_len1 = VARSIZE_ANY_EXHDR(fc_arg1);
	fc_len2 = VARSIZE_ANY_EXHDR(fc_arg2);

	fc_cmp = memcmp(VARDATA_ANY(fc_arg1), VARDATA_ANY(fc_arg2), Min(fc_len1, fc_len2));

	PG_FREE_IF_COPY(fc_arg1, 0);
	PG_FREE_IF_COPY(fc_arg2, 1);

	PG_RETURN_BOOL((fc_cmp < 0) || ((fc_cmp == 0) && (fc_len1 <= fc_len2)));
}

Datum byteagt(PG_FUNCTION_ARGS)
{
	bytea	   *fc_arg1 = PG_GETARG_BYTEA_PP(0);
	bytea	   *fc_arg2 = PG_GETARG_BYTEA_PP(1);
	int			fc_len1,
				fc_len2;
	int			fc_cmp;

	fc_len1 = VARSIZE_ANY_EXHDR(fc_arg1);
	fc_len2 = VARSIZE_ANY_EXHDR(fc_arg2);

	fc_cmp = memcmp(VARDATA_ANY(fc_arg1), VARDATA_ANY(fc_arg2), Min(fc_len1, fc_len2));

	PG_FREE_IF_COPY(fc_arg1, 0);
	PG_FREE_IF_COPY(fc_arg2, 1);

	PG_RETURN_BOOL((fc_cmp > 0) || ((fc_cmp == 0) && (fc_len1 > fc_len2)));
}

Datum byteage(PG_FUNCTION_ARGS)
{
	bytea	   *fc_arg1 = PG_GETARG_BYTEA_PP(0);
	bytea	   *fc_arg2 = PG_GETARG_BYTEA_PP(1);
	int			fc_len1,
				fc_len2;
	int			fc_cmp;

	fc_len1 = VARSIZE_ANY_EXHDR(fc_arg1);
	fc_len2 = VARSIZE_ANY_EXHDR(fc_arg2);

	fc_cmp = memcmp(VARDATA_ANY(fc_arg1), VARDATA_ANY(fc_arg2), Min(fc_len1, fc_len2));

	PG_FREE_IF_COPY(fc_arg1, 0);
	PG_FREE_IF_COPY(fc_arg2, 1);

	PG_RETURN_BOOL((fc_cmp > 0) || ((fc_cmp == 0) && (fc_len1 >= fc_len2)));
}

Datum byteacmp(PG_FUNCTION_ARGS)
{
	bytea	   *fc_arg1 = PG_GETARG_BYTEA_PP(0);
	bytea	   *fc_arg2 = PG_GETARG_BYTEA_PP(1);
	int			fc_len1,
				fc_len2;
	int			fc_cmp;

	fc_len1 = VARSIZE_ANY_EXHDR(fc_arg1);
	fc_len2 = VARSIZE_ANY_EXHDR(fc_arg2);

	fc_cmp = memcmp(VARDATA_ANY(fc_arg1), VARDATA_ANY(fc_arg2), Min(fc_len1, fc_len2));
	if ((fc_cmp == 0) && (fc_len1 != fc_len2))
		fc_cmp = (fc_len1 < fc_len2) ? -1 : 1;

	PG_FREE_IF_COPY(fc_arg1, 0);
	PG_FREE_IF_COPY(fc_arg2, 1);

	PG_RETURN_INT32(fc_cmp);
}

Datum bytea_sortsupport(PG_FUNCTION_ARGS)
{
	SortSupport fc_ssup = (SortSupport) PG_GETARG_POINTER(0);
	MemoryContext fc_oldcontext;

	fc_oldcontext = MemoryContextSwitchTo(fc_ssup->ssup_cxt);

	/* 使用通用字符串 SortSupport，强制使用 "C" 排序 */
	varstr_sortsupport(fc_ssup, BYTEAOID, C_COLLATION_OID);

	MemoryContextSwitchTo(fc_oldcontext);

	PG_RETURN_VOID();
}

/*
 * appendStringInfoText
 *
 * 将文本附加到str。
 * 类似于appendStringInfoString(str, text_to_cstring(t))但更快。
 */
static void fc_appendStringInfoText(StringInfo fc_str, const text *fc_t)
{
	appendBinaryStringInfo(fc_str, VARDATA_ANY(fc_t), VARSIZE_ANY_EXHDR(fc_t));
}

/*
 * replace_text
 * 用 'new_sub_str' 替换 'orig_str' 中所有出现的 'old_sub_str'
 * 形成 'new_str'
 *
 * 如果 'old_sub_str' == '' 或 'orig_str' == '' 返回 'orig_str'
 * 否则返回 'new_str'
 */
Datum replace_text(PG_FUNCTION_ARGS)
{
	text	   *fc_src_text = PG_GETARG_TEXT_PP(0);
	text	   *fc_from_sub_text = PG_GETARG_TEXT_PP(1);
	text	   *fc_to_sub_text = PG_GETARG_TEXT_PP(2);
	int			fc_src_text_len;
	int			fc_from_sub_text_len;
	TextPositionState fc_state;
	text	   *fc_ret_text;
	int			fc_chunk_len;
	char	   *fc_curr_ptr;
	char	   *fc_start_ptr;
	StringInfoData fc_str;
	bool		fc_found;

	fc_src_text_len = VARSIZE_ANY_EXHDR(fc_src_text);
	fc_from_sub_text_len = VARSIZE_ANY_EXHDR(fc_from_sub_text);

	/* 如果源字符串或模式为空，返回未修改的源字符串 */
	if (fc_src_text_len < 1 || fc_from_sub_text_len < 1)
	{
		PG_RETURN_TEXT_P(fc_src_text);
	}

	fc_text_position_setup(fc_src_text, fc_from_sub_text, PG_GET_COLLATION(), &fc_state);

	fc_found = fc_text_position_next(&fc_state);

	/* 当未找到 from_sub_text 时，无需操作。 */
	if (!fc_found)
	{
		fc_text_position_cleanup(&fc_state);
		PG_RETURN_TEXT_P(fc_src_text);
	}
	fc_curr_ptr = fc_text_position_get_match_ptr(&fc_state);
	fc_start_ptr = VARDATA_ANY(fc_src_text);

	initStringInfo(&fc_str);

	do
	{
		CHECK_FOR_INTERRUPTS();

		/* 复制上次 text_position_next() 跳过的数据 */
		fc_chunk_len = fc_curr_ptr - fc_start_ptr;
		appendBinaryStringInfo(&fc_str, fc_start_ptr, fc_chunk_len);

		fc_appendStringInfoText(&fc_str, fc_to_sub_text);

		fc_start_ptr = fc_curr_ptr + fc_from_sub_text_len;

		fc_found = fc_text_position_next(&fc_state);
		if (fc_found)
			fc_curr_ptr = fc_text_position_get_match_ptr(&fc_state);
	}
	while (fc_found);

	/* 复制尾随数据 */
	fc_chunk_len = ((char *) fc_src_text + VARSIZE_ANY(fc_src_text)) - fc_start_ptr;
	appendBinaryStringInfo(&fc_str, fc_start_ptr, fc_chunk_len);

	fc_text_position_cleanup(&fc_state);

	fc_ret_text = cstring_to_text_with_len(fc_str.data, fc_str.len);
	pfree(fc_str.data);

	PG_RETURN_TEXT_P(fc_ret_text);
}

/*
 * check_replace_text_has_escape
 *
 * 如果文本不包含需要处理的反斜杠，则返回0。
 * 如果文本包含反斜杠，但不包含正则表达式子匹配说明符，则返回1。
 * 如果文本包含正则表达式子匹配说明符（\1 .. \9），则返回2。
 */
static int fc_check_replace_text_has_escape(const text *fc_replace_text)
{
	int			fc_result = 0;
	const char *fc_p = VARDATA_ANY(fc_replace_text);
	const char *fc_p_end = fc_p + VARSIZE_ANY_EXHDR(fc_replace_text);

	while (fc_p < fc_p_end)
	{
		/* 查找下一个转义字符（如果有）。 */
		fc_p = memchr(fc_p, '\\', fc_p_end - fc_p);
		if (fc_p == NULL)
			break;
		fc_p++;
		/* 注意：末尾的反斜杠不需要额外处理。 */
		if (fc_p < fc_p_end)
		{
			if (*fc_p >= '1' && *fc_p <= '9')
				return 2;		/* 找到了一个子匹配说明符，因此完成 */
			fc_result = 1;			/* 找到其他序列，继续查找 */
			fc_p++;
		}
	}
	return fc_result;
}

/*
 * appendStringInfoRegexpSubstr
 *
 * 将 replace_text 附加到 str，将正则表达式回引用替换为
 * \n 转义字符。 start_ptr 是源字符串中匹配的开始位置，
 * 位于逻辑字符位置 data_pos。
 */
static void fc_appendStringInfoRegexpSubstr(StringInfo fc_str, text *fc_replace_text,
							 regmatch_t *fc_pmatch,
							 char *fc_start_ptr, int fc_data_pos)
{
	const char *fc_p = VARDATA_ANY(fc_replace_text);
	const char *fc_p_end = fc_p + VARSIZE_ANY_EXHDR(fc_replace_text);

	while (fc_p < fc_p_end)
	{
		const char *fc_chunk_start = fc_p;
		int			fc_so;
		int			fc_eo;

		/* 查找下一个转义字符（如果有）。 */
		fc_p = memchr(fc_p, '\\', fc_p_end - fc_p);
		if (fc_p == NULL)
			fc_p = fc_p_end;

		/* 复制我们刚刚扫描过的文本（如果有）。 */
		if (fc_p > fc_chunk_start)
			appendBinaryStringInfo(fc_str, fc_chunk_start, fc_p - fc_chunk_start);

		/* 如果到达字符串末尾，则完成，否则继续转义字符。 */
		if (fc_p >= fc_p_end)
			break;
		fc_p++;

		if (fc_p >= fc_p_end)
		{
			/* 输入的最后一个字符处发生转义。将其视为意外字符 */
			appendStringInfoChar(fc_str, '\\');
			break;
		}

		if (*fc_p >= '1' && *fc_p <= '9')
		{
			/* 使用正则表达式的回引用。 */
			int			fc_idx = *fc_p - '0';

			fc_so = fc_pmatch[fc_idx].rm_so;
			fc_eo = fc_pmatch[fc_idx].rm_eo;
			fc_p++;
		}
		else if (*fc_p == '&')
		{
			/* 使用整个匹配字符串。 */
			fc_so = fc_pmatch[0].rm_so;
			fc_eo = fc_pmatch[0].rm_eo;
			fc_p++;
		}
		else if (*fc_p == '\\')
		{
			/* \\ 表示将一个 \ 转移到输出。 */
			appendStringInfoChar(fc_str, '\\');
			fc_p++;
			continue;
		}
		else
		{
			/*
			 * 如果转义字符后面没有预期字符，只需将其视为
			 * 普通数据进行复制。（XXX抛出错误会更好吗？）
			 */
			appendStringInfoChar(fc_str, '\\');
			continue;
		}

		if (fc_so >= 0 && fc_eo >= 0)
		{
			/*
			 * 复制作为正则表达式的回引用的文本。注意 so 和 eo
			 * 是以字符而不是字节计数的。
			 */
			char	   *fc_chunk_start;
			int			fc_chunk_len;

			Assert(fc_so >= fc_data_pos);
			fc_chunk_start = fc_start_ptr;
			fc_chunk_start += fc_charlen_to_bytelen(fc_chunk_start, fc_so - fc_data_pos);
			fc_chunk_len = fc_charlen_to_bytelen(fc_chunk_start, fc_eo - fc_so);
			appendBinaryStringInfo(fc_str, fc_chunk_start, fc_chunk_len);
		}
	}
}

/*
 * replace_text_regexp
 *
 * 用 replace_text 替换 src_text 中与模式匹配的子字符串。
 * replace_text 可以包含反斜杠标记以替代
 * （部分）匹配文本。
 *
 * cflags: 正则表达式编译标志。
 * collation: 要使用的排序规则。
 * search_start: 在 src_text 中开始搜索的字符（不是字节）偏移量。
 * n: 如果为0，则替换所有匹配；如果 > 0，则仅替换第 N 次匹配。
 */
text * replace_text_regexp(text *fc_src_text, text *fc_pattern_text,
					text *fc_replace_text,
					int fc_cflags, Oid fc_collation,
					int fc_search_start, int fc_n)
{
	text	   *fc_ret_text;
	regex_t    *fc_re;
	int			fc_src_text_len = VARSIZE_ANY_EXHDR(fc_src_text);
	int			fc_nmatches = 0;
	StringInfoData fc_buf;
	regmatch_t	fc_pmatch[10];		/* 主匹配，加上 \1 到 \9 */
	int			fc_nmatch = lengthof(fc_pmatch);
	pg_wchar   *fc_data;
	size_t		fc_data_len;
	int			fc_data_pos;
	char	   *fc_start_ptr;
	int			fc_escape_status;

	initStringInfo(&fc_buf);

	/* 将数据字符串转换为宽字符。 */
	fc_data = (pg_wchar *) palloc((fc_src_text_len + 1) * sizeof(pg_wchar));
	fc_data_len = pg_mb2wchar_with_len(VARDATA_ANY(fc_src_text), fc_data, fc_src_text_len);

	/* 检查 replace_text 是否有转义，特别是正则表达式子匹配。 */
	fc_escape_status = fc_check_replace_text_has_escape(fc_replace_text);

	/* 如果没有正则表达式子匹配，我们可以使用 REG_NOSUB。 */
	if (fc_escape_status < 2)
	{
		fc_cflags |= REG_NOSUB;
		/* 还告诉 pg_regexec 我们只想要整个匹配的位置。 */
		fc_nmatch = 1;
	}

	/* 准备正则表达式。 */
	fc_re = RE_compile_and_cache(fc_pattern_text, fc_cflags, fc_collation);

	/* start_ptr 指向 src_text 中的 data_pos' 字符 */
	fc_start_ptr = (char *) VARDATA_ANY(fc_src_text);
	fc_data_pos = 0;

	while (fc_search_start <= fc_data_len)
	{
		int			fc_regexec_result;

		CHECK_FOR_INTERRUPTS();

		fc_regexec_result = pg_regexec(fc_re,
									fc_data,
									fc_data_len,
									fc_search_start,
									NULL,	/* 无细节 */
									fc_nmatch,
									fc_pmatch,
									0);

		if (fc_regexec_result == REG_NOMATCH)
			break;

		if (fc_regexec_result != REG_OKAY)
		{
			char		fc_errMsg[100];

			CHECK_FOR_INTERRUPTS();
			pg_regerror(fc_regexec_result, fc_re, fc_errMsg, sizeof(fc_errMsg));
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
					 errmsg("regular expression failed: %s", fc_errMsg)));
		}

		/*
		 * 计数匹配，并决定是否替换此匹配。
		 */
		fc_nmatches++;
		if (fc_n > 0 && fc_nmatches != fc_n)
		{
			/*
			 * 不，所以提前搜索开始，但不移动 start_ptr/data_pos。 （因此，
			 * 我们将匹配的文本视为未匹配，并稍后将其复制到输出。）
			 */
			fc_search_start = fc_pmatch[0].rm_eo;
			if (fc_pmatch[0].rm_so == fc_pmatch[0].rm_eo)
				fc_search_start++;
			continue;
		}

		/*
		 * 复制匹配位置左侧的文本。 注意，我们给出的
		 * 是字符而不是字节索引。
		 */
		if (fc_pmatch[0].rm_so - fc_data_pos > 0)
		{
			int			fc_chunk_len;

			fc_chunk_len = fc_charlen_to_bytelen(fc_start_ptr,
										   fc_pmatch[0].rm_so - fc_data_pos);
			appendBinaryStringInfo(&fc_buf, fc_start_ptr, fc_chunk_len);

			/*
			 * 将 start_ptr 向前移动到该文本，以避免多次重新扫描
			 * 它，如果 replace_text 包含多个回溯引用。
			 */
			fc_start_ptr += fc_chunk_len;
			fc_data_pos = fc_pmatch[0].rm_so;
		}

		/*
		 * 复制 replace_text，处理任何存在的转义字符。
		 */
		if (fc_escape_status > 0)
			fc_appendStringInfoRegexpSubstr(&fc_buf, fc_replace_text, fc_pmatch,
										 fc_start_ptr, fc_data_pos);
		else
			fc_appendStringInfoText(&fc_buf, fc_replace_text);

		/* 将 start_ptr 和 data_pos 移动到匹配的文本上。 */
		fc_start_ptr += fc_charlen_to_bytelen(fc_start_ptr,
										fc_pmatch[0].rm_eo - fc_data_pos);
		fc_data_pos = fc_pmatch[0].rm_eo;

		/*
		 * 如果我们只想替换一个实例，那么我们就完成了。
		 */
		if (fc_n > 0)
			break;

		/*
		 * 前进搜索位置。通常我们在上一个匹配的结束处开始下一个搜索；但如果匹配是零长度的，我们必须前进一个字符，否则我们就会再次找到相同的匹配。
		 */
		fc_search_start = fc_data_pos;
		if (fc_pmatch[0].rm_so == fc_pmatch[0].rm_eo)
			fc_search_start++;
	}

	/*
	 * 复制最后匹配右侧的文本。
	 */
	if (fc_data_pos < fc_data_len)
	{
		int			fc_chunk_len;

		fc_chunk_len = ((char *) fc_src_text + VARSIZE_ANY(fc_src_text)) - fc_start_ptr;
		appendBinaryStringInfo(&fc_buf, fc_start_ptr, fc_chunk_len);
	}

	fc_ret_text = cstring_to_text_with_len(fc_buf.data, fc_buf.len);
	pfree(fc_buf.data);
	pfree(fc_data);

	return fc_ret_text;
}

/*
 * split_part
 * 根据提供的字段分隔符解析输入字符串
 * 返回第 N 个项（基于 1，负计数从结束）
 */
Datum split_part(PG_FUNCTION_ARGS)
{
	text	   *fc_inputstring = PG_GETARG_TEXT_PP(0);
	text	   *fc_fldsep = PG_GETARG_TEXT_PP(1);
	int			fc_fldnum = PG_GETARG_INT32(2);
	int			fc_inputstring_len;
	int			fc_fldsep_len;
	TextPositionState fc_state;
	char	   *fc_start_ptr;
	char	   *fc_end_ptr;
	text	   *fc_result_text;
	bool		fc_found;

	/* 字段编号基于 1 */
	if (fc_fldnum == 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("field position must not be zero")));

	fc_inputstring_len = VARSIZE_ANY_EXHDR(fc_inputstring);
	fc_fldsep_len = VARSIZE_ANY_EXHDR(fc_fldsep);

	/* 对于空输入字符串返回空字符串 */
	if (fc_inputstring_len < 1)
		PG_RETURN_TEXT_P(cstring_to_text(""));

	/* 处理空字段分隔符 */
	if (fc_fldsep_len < 1)
	{
		/* 如果是第一个或最后一个字段，返回输入字符串，否则返回空字符串 */
		if (fc_fldnum == 1 || fc_fldnum == -1)
			PG_RETURN_TEXT_P(fc_inputstring);
		else
			PG_RETURN_TEXT_P(cstring_to_text(""));
	}

	/* 找到第一个字段分隔符 */
	fc_text_position_setup(fc_inputstring, fc_fldsep, PG_GET_COLLATION(), &fc_state);

	fc_found = fc_text_position_next(&fc_state);

	/* 特殊情况，如果 fldsep 完全没有找到 */
	if (!fc_found)
	{
		fc_text_position_cleanup(&fc_state);
		/* 如果是第一个或最后一个字段，返回输入字符串，否则返回空字符串 */
		if (fc_fldnum == 1 || fc_fldnum == -1)
			PG_RETURN_TEXT_P(fc_inputstring);
		else
			PG_RETURN_TEXT_P(cstring_to_text(""));
	}

	/*
	 * 处理负字段编号（即从右边计数）的方法是
	 * 转换为正字段编号；我们需要字段的总数
	 */
	if (fc_fldnum < 0)
	{
		/* 我们找到了一 个 fldsep，所以至少有两个字段 */
		int			fc_numfields = 2;

		while (fc_text_position_next(&fc_state))
			fc_numfields++;

		/* 最后一个字段的特殊情况无需额外的遍历 */
		if (fc_fldnum == -1)
		{
			fc_start_ptr = fc_text_position_get_match_ptr(&fc_state) + fc_fldsep_len;
			fc_end_ptr = VARDATA_ANY(fc_inputstring) + fc_inputstring_len;
			fc_text_position_cleanup(&fc_state);
			PG_RETURN_TEXT_P(cstring_to_text_with_len(fc_start_ptr,
													  fc_end_ptr - fc_start_ptr));
		}

		/* 否则，将 fldnum 转换为正表示法 */
		fc_fldnum += fc_numfields + 1;

		/* 如果字段不存在，返回空字符串 */
		if (fc_fldnum <= 0)
		{
			fc_text_position_cleanup(&fc_state);
			PG_RETURN_TEXT_P(cstring_to_text(""));
		}

		/* 重置为指向第一个匹配，但现在是正的 fldnum */
		fc_text_position_reset(&fc_state);
		fc_found = fc_text_position_next(&fc_state);
		Assert(fc_found);
	}

	/* 确定第一个字段的边界 */
	fc_start_ptr = VARDATA_ANY(fc_inputstring);
	fc_end_ptr = fc_text_position_get_match_ptr(&fc_state);

	while (fc_found && --fc_fldnum > 0)
	{
		/* 确定下一个字段的边界 */
		fc_start_ptr = fc_end_ptr + fc_fldsep_len;
		fc_found = fc_text_position_next(&fc_state);
		if (fc_found)
			fc_end_ptr = fc_text_position_get_match_ptr(&fc_state);
	}

	fc_text_position_cleanup(&fc_state);

	if (fc_fldnum > 0)
	{
		/* 找不到第 N 个字段分隔符 */
		/* 如果请求最后一个字段，返回它，否则返回空字符串 */
		if (fc_fldnum == 1)
		{
			int			fc_last_len = fc_start_ptr - VARDATA_ANY(fc_inputstring);

			fc_result_text = cstring_to_text_with_len(fc_start_ptr,
												   fc_inputstring_len - fc_last_len);
		}
		else
			fc_result_text = cstring_to_text("");
	}
	else
	{
		/* 请求非最后字段 */
		fc_result_text = cstring_to_text_with_len(fc_start_ptr, fc_end_ptr - fc_start_ptr);
	}

	PG_RETURN_TEXT_P(fc_result_text);
}

/*
 * 方便函数，当两个文本参数相等时返回真。
 */
static bool fc_text_isequal(text *fc_txt1, text *fc_txt2, Oid fc_collid)
{
	return DatumGetBool(DirectFunctionCall2Coll(texteq,
												fc_collid,
												PointerGetDatum(fc_txt1),
												PointerGetDatum(fc_txt2)));
}

/*
 * text_to_array
 * 解析输入字符串并返回文本数组元素，
 * 根据提供的字段分隔符
 */
Datum text_to_array(PG_FUNCTION_ARGS)
{
	SplitTextOutputData fc_tstate;

	/* 对于数组输出，tstate 应该初始为全零 */
	memset(&fc_tstate, 0, sizeof(fc_tstate));

	if (!fc_split_text(fcinfo, &fc_tstate))
		PG_RETURN_NULL();

	if (fc_tstate.astate == NULL)
		PG_RETURN_ARRAYTYPE_P(construct_empty_array(TEXTOID));

	PG_RETURN_ARRAYTYPE_P(makeArrayResult(fc_tstate.astate,
										  CurrentMemoryContext));
}

/*
 * text_to_array_null
 * 解析输入字符串并返回文本数组元素，
 * 基于提供的字段分隔符和空字符串
 *
 * 这是一条单独的入口点，仅用于防止回归测试
 * 抱怨同一内部函数的不同参数集。
 */
Datum text_to_array_null(PG_FUNCTION_ARGS)
{
	return text_to_array(fcinfo);
}

/*
 * text_to_table
 * 解析输入字符串并返回元素表，
 * 根据提供的字段分隔符
 */
Datum text_to_table(PG_FUNCTION_ARGS)
{
	ReturnSetInfo *fc_rsi = (ReturnSetInfo *) fcinfo->resultinfo;
	SplitTextOutputData fc_tstate;

	fc_tstate.astate = NULL;
	InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC);
	fc_tstate.tupstore = fc_rsi->setResult;
	fc_tstate.tupdesc = fc_rsi->setDesc;

	(void) fc_split_text(fcinfo, &fc_tstate);

	return (Datum) 0;
}

/*
 * text_to_table_null
 * 解析输入字符串并返回元素表，
 * 基于提供的字段分隔符和空字符串
 *
 * 这是一个单独的入口点，仅为了防止回归测试
 * 抱怨相同内部函数的不同参数集。
 */
Datum text_to_table_null(PG_FUNCTION_ARGS)
{
	return text_to_table(fcinfo);
}

/*
 * text_to_array、text_to_array_null、text_to_table
 * 和 text_to_table_null 函数的公共代码。
 *
 * 这些不是严格的，因此我们必须显式测试空输入。
 * 如果结果为 null，则返回 false，否则返回 true。
 *
 * 请注意，如果结果有效但为空（零个元素），我们将返回
 * 而不更改 *tstate --- 调用者也必须处理这种情况。
 */
static bool fc_split_text(FunctionCallInfo fcinfo, SplitTextOutputData *fc_tstate)
{
	text	   *fc_inputstring;
	text	   *fc_fldsep;
	text	   *fc_null_string;
	Oid			fc_collation = PG_GET_COLLATION();
	int			fc_inputstring_len;
	int			fc_fldsep_len;
	char	   *fc_start_ptr;
	text	   *fc_result_text;

	/* 当输入字符串为 NULL 时，结果也为 NULL */
	if (PG_ARGISNULL(0))
		return false;

	fc_inputstring = PG_GETARG_TEXT_PP(0);

	/* fldsep 可以是 NULL */
	if (!PG_ARGISNULL(1))
		fc_fldsep = PG_GETARG_TEXT_PP(1);
	else
		fc_fldsep = NULL;

	/* null_string 可以是 NULL 或省略 */
	if (PG_NARGS() > 2 && !PG_ARGISNULL(2))
		fc_null_string = PG_GETARG_TEXT_PP(2);
	else
		fc_null_string = NULL;

	if (fc_fldsep != NULL)
	{
		/*
		 * 使用非空 fldsep 的正常情况。 使用 text_position 机制
		 * 搜索 fldsep 的出现。
		 */
		TextPositionState fc_state;

		fc_inputstring_len = VARSIZE_ANY_EXHDR(fc_inputstring);
		fc_fldsep_len = VARSIZE_ANY_EXHDR(fc_fldsep);

		/* 对于空输入字符串返回空集合 */
		if (fc_inputstring_len < 1)
			return true;

		/* 空字段分隔符：将输入字符串作为单元素集合返回 */
		if (fc_fldsep_len < 1)
		{
			fc_split_text_accum_result(fc_tstate, fc_inputstring,
									fc_null_string, fc_collation);
			return true;
		}

		fc_text_position_setup(fc_inputstring, fc_fldsep, fc_collation, &fc_state);

		fc_start_ptr = VARDATA_ANY(fc_inputstring);

		for (;;)
		{
			bool		fc_found;
			char	   *fc_end_ptr;
			int			fc_chunk_len;

			CHECK_FOR_INTERRUPTS();

			fc_found = fc_text_position_next(&fc_state);
			if (!fc_found)
			{
				/* 获取最后一个字段 */
				fc_chunk_len = ((char *) fc_inputstring + VARSIZE_ANY(fc_inputstring)) - fc_start_ptr;
				fc_end_ptr = NULL; /* 未使用，但某些编译器抱怨 */
			}
			else
			{
				/* 获取非最后一个字段 */
				fc_end_ptr = fc_text_position_get_match_ptr(&fc_state);
				fc_chunk_len = fc_end_ptr - fc_start_ptr;
			}

			/* 构建一个临时文本数据项以传递给 split_text_accum_result */
			fc_result_text = cstring_to_text_with_len(fc_start_ptr, fc_chunk_len);

			/* 储存这个字段 */
			fc_split_text_accum_result(fc_tstate, fc_result_text,
									fc_null_string, fc_collation);

			pfree(fc_result_text);

			if (!fc_found)
				break;

			fc_start_ptr = fc_end_ptr + fc_fldsep_len;
		}

		fc_text_position_cleanup(&fc_state);
	}
	else
	{
		/*
		 * 当 fldsep 为 NULL 时，输入字符串中的每个字符都成为
		 * 结果集中的单独元素。 分隔符实际上是
		 * 字符之间的空格。
		 */
		fc_inputstring_len = VARSIZE_ANY_EXHDR(fc_inputstring);

		fc_start_ptr = VARDATA_ANY(fc_inputstring);

		while (fc_inputstring_len > 0)
		{
			int			fc_chunk_len = pg_mblen(fc_start_ptr);

			CHECK_FOR_INTERRUPTS();

			/* 构建一个临时文本数据项以传递给 split_text_accum_result */
			fc_result_text = cstring_to_text_with_len(fc_start_ptr, fc_chunk_len);

			/* 储存这个字段 */
			fc_split_text_accum_result(fc_tstate, fc_result_text,
									fc_null_string, fc_collation);

			pfree(fc_result_text);

			fc_start_ptr += fc_chunk_len;
			fc_inputstring_len -= fc_chunk_len;
		}
	}

	return true;
}

/*
 * 将文本项添加到结果集（表或数组）。
 *
 * 这也负责检查该项是否匹配
 * null_string，在这种情况下我们应该发出 NULL。
 */
static void fc_split_text_accum_result(SplitTextOutputData *fc_tstate,
						text *fc_field_value,
						text *fc_null_string,
						Oid fc_collation)
{
	bool		fc_is_null = false;

	if (fc_null_string && fc_text_isequal(fc_field_value, fc_null_string, fc_collation))
		fc_is_null = true;

	if (fc_tstate->tupstore)
	{
		Datum		fc_values[1];
		bool		fc_nulls[1];

		fc_values[0] = PointerGetDatum(fc_field_value);
		fc_nulls[0] = fc_is_null;

		tuplestore_putvalues(fc_tstate->tupstore,
							 fc_tstate->tupdesc,
							 fc_values,
							 fc_nulls);
	}
	else
	{
		fc_tstate->astate = accumArrayResult(fc_tstate->astate,
										  PointerGetDatum(fc_field_value),
										  fc_is_null,
										  TEXTOID,
										  CurrentMemoryContext);
	}
}

/*
 * array_to_text
 * 使用提供的字段分隔符
 * 连接输入数组元素的 Cstring 表示
 */
Datum array_to_text(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_v = PG_GETARG_ARRAYTYPE_P(0);
	char	   *fc_fldsep = text_to_cstring(PG_GETARG_TEXT_PP(1));

	PG_RETURN_TEXT_P(fc_array_to_text_internal(fcinfo, fc_v, fc_fldsep, NULL));
}

/*
 * array_to_text_null
 * 使用提供的字段分隔符和空字符串
 * 连接输入数组元素的 Cstring 表示
 *
 * 该版本不是严格的，因此我们必须显式测试空输入。
 */
Datum array_to_text_null(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_v;
	char	   *fc_fldsep;
	char	   *fc_null_string;

	/* 当第一个或第二个参数为 NULL 时返回 NULL */
	if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
		PG_RETURN_NULL();

	fc_v = PG_GETARG_ARRAYTYPE_P(0);
	fc_fldsep = text_to_cstring(PG_GETARG_TEXT_PP(1));

	/* NULL 空字符串作为空指针传递 */
	if (!PG_ARGISNULL(2))
		fc_null_string = text_to_cstring(PG_GETARG_TEXT_PP(2));
	else
		fc_null_string = NULL;

	PG_RETURN_TEXT_P(fc_array_to_text_internal(fcinfo, fc_v, fc_fldsep, fc_null_string));
}

/*
 * array_to_text 和 array_to_text_null 函数的公共代码
 */
static text * fc_array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *fc_v,
					   const char *fc_fldsep, const char *fc_null_string)
{
	text	   *fc_result;
	int			fc_nitems,
			   *fc_dims,
				fc_ndims;
	Oid			fc_element_type;
	int			fc_typlen;
	bool		fc_typbyval;
	char		fc_typalign;
	StringInfoData fc_buf;
	bool		fc_printed = false;
	char	   *fc_p;
	bits8	   *fc_bitmap;
	int			fc_bitmask;
	int			fc_i;
	ArrayMetaState *fc_my_extra;

	fc_ndims = ARR_NDIM(fc_v);
	fc_dims = ARR_DIMS(fc_v);
	fc_nitems = ArrayGetNItems(fc_ndims, fc_dims);

	/* 如果没有元素，返回一个空字符串 */
	if (fc_nitems == 0)
		return cstring_to_text_with_len("", 0);

	fc_element_type = ARR_ELEMTYPE(fc_v);
	initStringInfo(&fc_buf);

	/*
	 * 我们安排只在每一系列调用中查找元素类型的信息，包括其输出
	 * 转换过程，假设元素类型不会在我们之下发生变化。
	 */
	fc_my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
	if (fc_my_extra == NULL)
	{
		fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
													  sizeof(ArrayMetaState));
		fc_my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
		fc_my_extra->element_type = ~fc_element_type;
	}

	if (fc_my_extra->element_type != fc_element_type)
	{
		/*
		 * 获取元素类型的信息，包括其输出转换过程
		 */
		get_type_io_data(fc_element_type, IOFunc_output,
						 &fc_my_extra->typlen, &fc_my_extra->typbyval,
						 &fc_my_extra->typalign, &fc_my_extra->typdelim,
						 &fc_my_extra->typioparam, &fc_my_extra->typiofunc);
		fmgr_info_cxt(fc_my_extra->typiofunc, &fc_my_extra->proc,
					  fcinfo->flinfo->fn_mcxt);
		fc_my_extra->element_type = fc_element_type;
	}
	fc_typlen = fc_my_extra->typlen;
	fc_typbyval = fc_my_extra->typbyval;
	fc_typalign = fc_my_extra->typalign;

	fc_p = ARR_DATA_PTR(fc_v);
	fc_bitmap = ARR_NULLBITMAP(fc_v);
	fc_bitmask = 1;

	for (fc_i = 0; fc_i < fc_nitems; fc_i++)
	{
		Datum		fc_itemvalue;
		char	   *fc_value;

		/* 获取源元素，检查是否为 NULL */
		if (fc_bitmap && (*fc_bitmap & fc_bitmask) == 0)
		{
			/* 如果 null_string 为 NULL，我们将忽略空元素 */
			if (fc_null_string != NULL)
			{
				if (fc_printed)
					appendStringInfo(&fc_buf, "%s%s", fc_fldsep, fc_null_string);
				else
					appendStringInfoString(&fc_buf, fc_null_string);
				fc_printed = true;
			}
		}
		else
		{
			fc_itemvalue = fetch_att(fc_p, fc_typbyval, fc_typlen);

			fc_value = OutputFunctionCall(&fc_my_extra->proc, fc_itemvalue);

			if (fc_printed)
				appendStringInfo(&fc_buf, "%s%s", fc_fldsep, fc_value);
			else
				appendStringInfoString(&fc_buf, fc_value);
			fc_printed = true;

			fc_p = att_addlength_pointer(fc_p, fc_typlen, fc_p);
			fc_p = (char *) att_align_nominal(fc_p, fc_typalign);
		}

		/* 如果有，向前推进位图指针 */
		if (fc_bitmap)
		{
			fc_bitmask <<= 1;
			if (fc_bitmask == 0x100)
			{
				fc_bitmap++;
				fc_bitmask = 1;
			}
		}
	}

	fc_result = cstring_to_text_with_len(fc_buf.data, fc_buf.len);
	pfree(fc_buf.data);

	return fc_result;
}

#define HEXBASE 16
/*
 * 将一个 int32 转换为包含数字的基数 16（十六进制）表示的字符串。
 */
Datum to_hex32(PG_FUNCTION_ARGS)
{
	uint32		fc_value = (uint32) PG_GETARG_INT32(0);
	char	   *fc_ptr;
	const char *fc_digits = "0123456789abcdef";
	char		fc_buf[32];		/* 比需要的大，但合理 */

	fc_ptr = fc_buf + sizeof(fc_buf) - 1;
	*fc_ptr = '\0';

	do
	{
		*--fc_ptr = fc_digits[fc_value % HEXBASE];
		fc_value /= HEXBASE;
	} while (fc_ptr > fc_buf && fc_value);

	PG_RETURN_TEXT_P(cstring_to_text(fc_ptr));
}

/*
 * 将一个 int64 转换为包含数字的基数 16（十六进制）表示的字符串。
 */
Datum to_hex64(PG_FUNCTION_ARGS)
{
	uint64		fc_value = (uint64) PG_GETARG_INT64(0);
	char	   *fc_ptr;
	const char *fc_digits = "0123456789abcdef";
	char		fc_buf[32];		/* 比需要的大，但合理 */

	fc_ptr = fc_buf + sizeof(fc_buf) - 1;
	*fc_ptr = '\0';

	do
	{
		*--fc_ptr = fc_digits[fc_value % HEXBASE];
		fc_value /= HEXBASE;
	} while (fc_ptr > fc_buf && fc_value);

	PG_RETURN_TEXT_P(cstring_to_text(fc_ptr));
}

/*
 * 返回一个数据项的大小，可能经过压缩
 *
 * 适用于任何数据类型
 */
Datum pg_column_size(PG_FUNCTION_ARGS)
{
	Datum		fc_value = PG_GETARG_DATUM(0);
	int32		fc_result;
	int			fc_typlen;

	/* 在第一次调用时，获取输入类型的 typlen，并保存在 *fn_extra */
	if (fcinfo->flinfo->fn_extra == NULL)
	{
		/* 查找所提供参数的数据类型 */
		Oid			fc_argtypeid = get_fn_expr_argtype(fcinfo->flinfo, 0);

		fc_typlen = get_typlen(fc_argtypeid);
		if (fc_typlen == 0)		/* 不应该发生 */
			elog(ERROR, "cache lookup failed for type %u", fc_argtypeid);

		fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
													  sizeof(int));
		*((int *) fcinfo->flinfo->fn_extra) = fc_typlen;
	}
	else
		fc_typlen = *((int *) fcinfo->flinfo->fn_extra);

	if (fc_typlen == -1)
	{
		/* varlena 类型，可能经过烘焙 */
		fc_result = toast_datum_size(fc_value);
	}
	else if (fc_typlen == -2)
	{
		/* cstring */
		fc_result = strlen(DatumGetCString(fc_value)) + 1;
	}
	else
	{
		/* 普通固定宽度类型 */
		fc_result = fc_typlen;
	}

	PG_RETURN_INT32(fc_result);
}

/*
 * 返回存储在压缩属性中的压缩方法。对于非 varlena 类型或未压缩数据，返回 NULL。
 */
Datum pg_column_compression(PG_FUNCTION_ARGS)
{
	int			fc_typlen;
	char	   *fc_result;
	ToastCompressionId fc_cmid;

	/* 在第一次调用时，获取输入类型的 typlen，并保存在 *fn_extra */
	if (fcinfo->flinfo->fn_extra == NULL)
	{
		/* 查找所提供参数的数据类型 */
		Oid			fc_argtypeid = get_fn_expr_argtype(fcinfo->flinfo, 0);

		fc_typlen = get_typlen(fc_argtypeid);
		if (fc_typlen == 0)		/* 不应该发生 */
			elog(ERROR, "cache lookup failed for type %u", fc_argtypeid);

		fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
													  sizeof(int));
		*((int *) fcinfo->flinfo->fn_extra) = fc_typlen;
	}
	else
		fc_typlen = *((int *) fcinfo->flinfo->fn_extra);

	if (fc_typlen != -1)
		PG_RETURN_NULL();

	/* 获取存储在压缩 varlena 中的压缩方法 ID */
	fc_cmid = toast_get_compression_id((struct varlena *)
									DatumGetPointer(PG_GETARG_DATUM(0)));
	if (fc_cmid == TOAST_INVALID_COMPRESSION_ID)
		PG_RETURN_NULL();

	/* 将压缩方法 ID 转换为压缩方法名称 */
	switch (fc_cmid)
	{
		case TOAST_PGLZ_COMPRESSION_ID:
			fc_result = "pglz";
			break;
		case TOAST_LZ4_COMPRESSION_ID:
			fc_result = "lz4";
			break;
		default:
			elog(ERROR, "invalid compression method id %d", fc_cmid);
	}

	PG_RETURN_TEXT_P(cstring_to_text(fc_result));
}

/*
 * string_agg - 连接值并返回字符串。
 *
 * 语法：string_agg(value text, delimiter text) 返回 text
 *
 * 注意：任何 NULL 值都会被忽略。第一次调用的分隔符实际上并不使用，后续调用时分隔符会在相关值之前。
 */

/* 初始化状态的子程序 */
static StringInfo fc_makeStringAggState(FunctionCallInfo fcinfo)
{
	StringInfo	fc_state;
	MemoryContext fc_aggcontext;
	MemoryContext fc_oldcontext;

	if (!AggCheckCallContext(fcinfo, &fc_aggcontext))
	{
		/* 由于内部类型参数，无法直接调用 */
		elog(ERROR, "string_agg_transfn called in non-aggregate context");
	}

	/*
	 * 在聚合上下文中创建状态。它将在后续调用中保持存在。
	 */
	fc_oldcontext = MemoryContextSwitchTo(fc_aggcontext);
	fc_state = makeStringInfo();
	MemoryContextSwitchTo(fc_oldcontext);

	return fc_state;
}

Datum string_agg_transfn(PG_FUNCTION_ARGS)
{
	StringInfo	fc_state;

	fc_state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);

	/* 除非为 null，否则附加该值。 */
	if (!PG_ARGISNULL(1))
	{
		/* 在第一次遍历时，我们忽略分隔符。 */
		if (fc_state == NULL)
			fc_state = fc_makeStringAggState(fcinfo);
		else if (!PG_ARGISNULL(2))
			fc_appendStringInfoText(fc_state, PG_GETARG_TEXT_PP(2));	/* 分隔符 */

		fc_appendStringInfoText(fc_state, PG_GETARG_TEXT_PP(1));	/* value */
	}

	/*
	 * string_agg()的转换类型被声明为"internal"，
	 * 这是一个与指针大小相同的按值传递类型。
	 */
	PG_RETURN_POINTER(fc_state);
}

Datum string_agg_finalfn(PG_FUNCTION_ARGS)
{
	StringInfo	fc_state;

	/* 由于内部类型参数，无法直接调用 */
	Assert(AggCheckCallContext(fcinfo, NULL));

	fc_state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);

	if (fc_state != NULL)
		PG_RETURN_TEXT_P(cstring_to_text_with_len(fc_state->data, fc_state->len));
	else
		PG_RETURN_NULL();
}

/*
 * 为类似 concat 的函数的参数的数据类型的输出函数准备带有 fmgr 信息的缓存，从“argidx”参数开始。
 * （在此之前的参数将在生成的 FmgrInfo 数组中具有相应的位置，但我们不会填充这些位置。）
 */
static FmgrInfo * fc_build_concat_foutcache(FunctionCallInfo fcinfo, int fc_argidx)
{
	FmgrInfo   *fc_foutcache;
	int			fc_i;

	/* 我们将信息保存在 fn_mcxt 中，以便它可以在调用之间存活 */
	fc_foutcache = (FmgrInfo *) MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
												PG_NARGS() * sizeof(FmgrInfo));

	for (fc_i = fc_argidx; fc_i < PG_NARGS(); fc_i++)
	{
		Oid			fc_valtype;
		Oid			fc_typOutput;
		bool		fc_typIsVarlena;

		fc_valtype = get_fn_expr_argtype(fcinfo->flinfo, fc_i);
		if (!OidIsValid(fc_valtype))
			elog(ERROR, "could not determine data type of concat() input");

		getTypeOutputInfo(fc_valtype, &fc_typOutput, &fc_typIsVarlena);
		fmgr_info_cxt(fc_typOutput, &fc_foutcache[fc_i], fcinfo->flinfo->fn_mcxt);
	}

	fcinfo->flinfo->fn_extra = fc_foutcache;

	return fc_foutcache;
}

/*
 * concat() 和 concat_ws() 的实现。
 *
 * sepstr 是放置在值之间的分隔字符串。
 * argidx 标识要连接的第一个参数（从零开始计数）；注意这一点在同一系列调用中必须是常量。
 *
 * 如果结果应为 NULL，则返回 NULL，否则返回文本值。
 */
static text * fc_concat_internal(const char *fc_sepstr, int fc_argidx,
				FunctionCallInfo fcinfo)
{
	text	   *fc_result;
	StringInfoData fc_str;
	FmgrInfo   *fc_foutcache;
	bool		fc_first_arg = true;
	int			fc_i;

	/*
	 * concat(VARIADIC some-array) 本质上等同于
	 * array_to_text()，即用给定的分隔符连接数组元素。
	 * 所以我们只是将案例交给那段代码。
	 */
	if (get_fn_expr_variadic(fcinfo->flinfo))
	{
		ArrayType  *fc_arr;

		/* 应该只有一个参数 */
		Assert(fc_argidx == PG_NARGS() - 1);

		/* concat(VARIADIC NULL) 被定义为 NULL */
		if (PG_ARGISNULL(fc_argidx))
			return NULL;

		/*
		 * 非NULL参数最好是一个数组。我们假设任何可以使get_fn_expr_variadic
		 * 返回true的调用上下文都将检查一个以VARIADIC标记的参数实际上是一个数组。
		 * 所以直接Assert它是一个数组而不是进行全面的错误检查应该是可以的。
		 */
		Assert(OidIsValid(get_base_element_type(get_fn_expr_argtype(fcinfo->flinfo, fc_argidx))));

		/* 好的，可以安全地获取数组值 */
		fc_arr = PG_GETARG_ARRAYTYPE_P(fc_argidx);

		/*
		 * 并序列化数组。我们告诉 array_to_text 忽略 null
		 * 元素，这与下面循环的行为相匹配。
		 */
		return fc_array_to_text_internal(fcinfo, fc_arr, fc_sepstr, NULL);
	}

	/* 没有显式 VARIADIC 标记的正常情况 */
	initStringInfo(&fc_str);

	/* 获取输出函数信息，如果是第一次则构建它 */
	fc_foutcache = (FmgrInfo *) fcinfo->flinfo->fn_extra;
	if (fc_foutcache == NULL)
		fc_foutcache = fc_build_concat_foutcache(fcinfo, fc_argidx);

	for (fc_i = fc_argidx; fc_i < PG_NARGS(); fc_i++)
	{
		if (!PG_ARGISNULL(fc_i))
		{
			Datum		fc_value = PG_GETARG_DATUM(fc_i);

			/* 如果合适则添加分隔符 */
			if (fc_first_arg)
				fc_first_arg = false;
			else
				appendStringInfoString(&fc_str, fc_sepstr);

			/* 调用适当的类型输出函数，追加结果 */
			appendStringInfoString(&fc_str,
								   OutputFunctionCall(&fc_foutcache[fc_i], fc_value));
		}
	}

	fc_result = cstring_to_text_with_len(fc_str.data, fc_str.len);
	pfree(fc_str.data);

	return fc_result;
}

/*
 * 连接所有参数。忽略 NULL 参数。
 */
Datum text_concat(PG_FUNCTION_ARGS)
{
	text	   *fc_result;

	fc_result = fc_concat_internal("", 0, fcinfo);
	if (fc_result == NULL)
		PG_RETURN_NULL();
	PG_RETURN_TEXT_P(fc_result);
}

/*
 * 连接除第一个参数值之外的所有参数值，使用分隔符。第一个
 * 参数用作分隔符。忽略 NULL 参数。
 */
Datum text_concat_ws(PG_FUNCTION_ARGS)
{
	char	   *fc_sep;
	text	   *fc_result;

	/* 当分隔符为 NULL 时返回 NULL */
	if (PG_ARGISNULL(0))
		PG_RETURN_NULL();
	fc_sep = text_to_cstring(PG_GETARG_TEXT_PP(0));

	fc_result = fc_concat_internal(fc_sep, 1, fcinfo);
	if (fc_result == NULL)
		PG_RETURN_NULL();
	PG_RETURN_TEXT_P(fc_result);
}

/*
 * 返回字符串中的前 n 个字符。当 n 为负时，
 * 返回除最后 |n| 个字符之外的所有字符。
 */
Datum text_left(PG_FUNCTION_ARGS)
{
	int			fc_n = PG_GETARG_INT32(1);

	if (fc_n < 0)
	{
		text	   *fc_str = PG_GETARG_TEXT_PP(0);
		const char *fc_p = VARDATA_ANY(fc_str);
		int			fc_len = VARSIZE_ANY_EXHDR(fc_str);
		int			fc_rlen;

		fc_n = pg_mbstrlen_with_len(fc_p, fc_len) + fc_n;
		fc_rlen = pg_mbcharcliplen(fc_p, fc_len, fc_n);
		PG_RETURN_TEXT_P(cstring_to_text_with_len(fc_p, fc_rlen));
	}
	else
		PG_RETURN_TEXT_P(fc_text_substring(PG_GETARG_DATUM(0), 1, fc_n, false));
}

/*
 * 返回字符串中的最后 n 个字符。当 n 为负时，
 * 返回除前 |n| 个字符之外的所有字符。
 */
Datum text_right(PG_FUNCTION_ARGS)
{
	text	   *fc_str = PG_GETARG_TEXT_PP(0);
	const char *fc_p = VARDATA_ANY(fc_str);
	int			fc_len = VARSIZE_ANY_EXHDR(fc_str);
	int			fc_n = PG_GETARG_INT32(1);
	int			fc_off;

	if (fc_n < 0)
		fc_n = -fc_n;
	else
		fc_n = pg_mbstrlen_with_len(fc_p, fc_len) - fc_n;
	fc_off = pg_mbcharcliplen(fc_p, fc_len, fc_n);

	PG_RETURN_TEXT_P(cstring_to_text_with_len(fc_p + fc_off, fc_len - fc_off));
}

/*
 * 返回反转的字符串
 */
Datum text_reverse(PG_FUNCTION_ARGS)
{
	text	   *fc_str = PG_GETARG_TEXT_PP(0);
	const char *fc_p = VARDATA_ANY(fc_str);
	int			fc_len = VARSIZE_ANY_EXHDR(fc_str);
	const char *fc_endp = fc_p + fc_len;
	text	   *fc_result;
	char	   *fc_dst;

	fc_result = palloc(fc_len + VARHDRSZ);
	fc_dst = (char *) VARDATA(fc_result) + fc_len;
	SET_VARSIZE(fc_result, fc_len + VARHDRSZ);

	if (pg_database_encoding_max_length() > 1)
	{
		/* 多字节版本 */
		while (fc_p < fc_endp)
		{
			int			fc_sz;

			fc_sz = pg_mblen(fc_p);
			fc_dst -= fc_sz;
			memcpy(fc_dst, fc_p, fc_sz);
			fc_p += fc_sz;
		}
	}
	else
	{
		/* 单字节版本 */
		while (fc_p < fc_endp)
			*(--fc_dst) = *fc_p++;
	}

	PG_RETURN_TEXT_P(fc_result);
}


/*
 * 支持 text_format() 的宏
 */
#define TEXT_FORMAT_FLAG_MINUS	0x0001	/* 是否存在负号标志？ */

#define ADVANCE_PARSE_POINTER(ptr,end_ptr) \
	do { \
		if (++(ptr) >= (end_ptr)) \
			ereport(ERROR, \
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE), \
					 errmsg("unterminated format() type specifier"), \
					 errhint("For a single \"%%\" use \"%%%%\"."))); \
	} while (0)

/*
 * 返回格式化字符串
 */
Datum text_format(PG_FUNCTION_ARGS)
{
	text	   *fc_fmt;
	StringInfoData fc_str;
	const char *fc_cp;
	const char *fc_start_ptr;
	const char *fc_end_ptr;
	text	   *fc_result;
	int			fc_arg;
	bool		fc_funcvariadic;
	int			fc_nargs;
	Datum	   *fc_elements = NULL;
	bool	   *fc_nulls = NULL;
	Oid			fc_element_type = InvalidOid;
	Oid			fc_prev_type = InvalidOid;
	Oid			fc_prev_width_type = InvalidOid;
	FmgrInfo	fc_typoutputfinfo;
	FmgrInfo	fc_typoutputinfo_width;

	/* 当格式字符串为 null 时，立即返回 null */
	if (PG_ARGISNULL(0))
		PG_RETURN_NULL();

	/* 如果参数被标记为 VARIADIC，则将数组展开为元素 */
	if (get_fn_expr_variadic(fcinfo->flinfo))
	{
		ArrayType  *fc_arr;
		int16		fc_elmlen;
		bool		fc_elmbyval;
		char		fc_elmalign;
		int			fc_nitems;

		/* 应该只有一个参数 */
		Assert(PG_NARGS() == 2);

		/* 如果参数为 NULL，我们将其视为零长度数组 */
		if (PG_ARGISNULL(1))
			fc_nitems = 0;
		else
		{
			/*
			 * 非空参数最好是一个数组。我们假设任何
			 * 调用上下文都可能让 get_fn_expr_variadic 返回 true，
			 * 将检查一个标记为 VARIADIC 的参数确实是
			 * 一个数组。因此，简单地断言它是一个数组应该是安全的，
			 * 而不是进行全面的错误检查。
			 */
			Assert(OidIsValid(get_base_element_type(get_fn_expr_argtype(fcinfo->flinfo, 1))));

			/* 好的，可以安全地获取数组值 */
			fc_arr = PG_GETARG_ARRAYTYPE_P(1);

			/* 获取数组元素类型的信息 */
			fc_element_type = ARR_ELEMTYPE(fc_arr);
			get_typlenbyvalalign(fc_element_type,
								 &fc_elmlen, &fc_elmbyval, &fc_elmalign);

			/* 提取所有数组元素 */
			deconstruct_array(fc_arr, fc_element_type, fc_elmlen, fc_elmbyval, fc_elmalign,
							  &fc_elements, &fc_nulls, &fc_nitems);
		}

		fc_nargs = fc_nitems + 1;
		fc_funcvariadic = true;
	}
	else
	{
		/* 非 VARIADIC 情况，我们将单独处理参数 */
		fc_nargs = PG_NARGS();
		fc_funcvariadic = false;
	}

	/* 为主循环设置。 */
	fc_fmt = PG_GETARG_TEXT_PP(0);
	fc_start_ptr = VARDATA_ANY(fc_fmt);
	fc_end_ptr = fc_start_ptr + VARSIZE_ANY_EXHDR(fc_fmt);
	initStringInfo(&fc_str);
	fc_arg = 1;					/* 下一参数位置以进行打印 */

	/* 扫描格式字符串，查找转换说明符。 */
	for (fc_cp = fc_start_ptr; fc_cp < fc_end_ptr; fc_cp++)
	{
		int			fc_argpos;
		int			fc_widthpos;
		int			fc_flags;
		int			fc_width;
		Datum		fc_value;
		bool		fc_isNull;
		Oid			fc_typid;

		/*
		 * 如果它不是转换说明符的开始，则将其复制到
		 * 输出缓冲区。
		 */
		if (*fc_cp != '%')
		{
			appendStringInfoCharMacro(&fc_str, *fc_cp);
			continue;
		}

		ADVANCE_PARSE_POINTER(fc_cp, fc_end_ptr);

		/* 简单情况：%% 输出一个单一的 % */
		if (*fc_cp == '%')
		{
			appendStringInfoCharMacro(&fc_str, *fc_cp);
			continue;
		}

		/* 解析格式说明符的可选部分 */
		fc_cp = fc_text_format_parse_format(fc_cp, fc_end_ptr,
									  &fc_argpos, &fc_widthpos,
									  &fc_flags, &fc_width);

		/*
		 * 接下来我们应该看到主转换说明符。无论是否
		 * 存在参数位置，已知此时字符串中至少还有一个
		 * 字符。经验表明，在尝试获取参数之前检查该字符是否
		 * 是预期的字符之一是值得的，以便尽可能减少对格式错误
		 * 说明符的混淆响应。
		 */
		if (strchr("sIL", *fc_cp) == NULL)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("unrecognized format() type specifier \"%.*s\"",
							pg_mblen(fc_cp), fc_cp),
					 errhint("For a single \"%%\" use \"%%%%\".")));

		/* 如果指定了间接宽度，则获取其值 */
		if (fc_widthpos >= 0)
		{
			/* 收集指定的或下一个参数位置 */
			if (fc_widthpos > 0)
				fc_arg = fc_widthpos;
			if (fc_arg >= fc_nargs)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("too few arguments for format()")));

			/* 获取选定参数的值和类型 */
			if (!fc_funcvariadic)
			{
				fc_value = PG_GETARG_DATUM(fc_arg);
				fc_isNull = PG_ARGISNULL(fc_arg);
				fc_typid = get_fn_expr_argtype(fcinfo->flinfo, fc_arg);
			}
			else
			{
				fc_value = fc_elements[fc_arg - 1];
				fc_isNull = fc_nulls[fc_arg - 1];
				fc_typid = fc_element_type;
			}
			if (!OidIsValid(fc_typid))
				elog(ERROR, "could not determine data type of format() input");

			fc_arg++;

			/* 我们可以将 NULL 宽度视为零 */
			if (fc_isNull)
				fc_width = 0;
			else if (fc_typid == INT4OID)
				fc_width = DatumGetInt32(fc_value);
			else if (fc_typid == INT2OID)
				fc_width = DatumGetInt16(fc_value);
			else
			{
				/* 对于不太常见的数据类型，先转换为文本再转为整数 */
				char	   *fc_str;

				if (fc_typid != fc_prev_width_type)
				{
					Oid			fc_typoutputfunc;
					bool		fc_typIsVarlena;

					getTypeOutputInfo(fc_typid, &fc_typoutputfunc, &fc_typIsVarlena);
					fmgr_info(fc_typoutputfunc, &fc_typoutputinfo_width);
					fc_prev_width_type = fc_typid;
				}

				fc_str = OutputFunctionCall(&fc_typoutputinfo_width, fc_value);

				/* pg_strtoint32 将会对错误数据或溢出进行投诉 */
				fc_width = pg_strtoint32(fc_str);

				pfree(fc_str);
			}
		}

		/* 收集指定的或下一个参数位置 */
		if (fc_argpos > 0)
			fc_arg = fc_argpos;
		if (fc_arg >= fc_nargs)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("too few arguments for format()")));

		/* 获取选定参数的值和类型 */
		if (!fc_funcvariadic)
		{
			fc_value = PG_GETARG_DATUM(fc_arg);
			fc_isNull = PG_ARGISNULL(fc_arg);
			fc_typid = get_fn_expr_argtype(fcinfo->flinfo, fc_arg);
		}
		else
		{
			fc_value = fc_elements[fc_arg - 1];
			fc_isNull = fc_nulls[fc_arg - 1];
			fc_typid = fc_element_type;
		}
		if (!OidIsValid(fc_typid))
			elog(ERROR, "could not determine data type of format() input");

		fc_arg++;

		/*
		 * 获取适当的 typOutput 函数，如果与
		 * 上一个参数类型相同，则重用上一个函数。这在
		 * VARIADIC-ARRAY 情况中特别有用，但即使对于普通调用
		 * 也经常节省工作。
		 */
		if (fc_typid != fc_prev_type)
		{
			Oid			fc_typoutputfunc;
			bool		fc_typIsVarlena;

			getTypeOutputInfo(fc_typid, &fc_typoutputfunc, &fc_typIsVarlena);
			fmgr_info(fc_typoutputfunc, &fc_typoutputfinfo);
			fc_prev_type = fc_typid;
		}

		/*
		 * 现在我们可以格式化这个值。
		 */
		switch (*fc_cp)
		{
			case 's':
			case 'I':
			case 'L':
				fc_text_format_string_conversion(&fc_str, *fc_cp, &fc_typoutputfinfo,
											  fc_value, fc_isNull,
											  fc_flags, fc_width);
				break;
			default:
				/* 不应该到达这里，因为之前有检查 */
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("unrecognized format() type specifier \"%.*s\"",
								pg_mblen(fc_cp), fc_cp),
						 errhint("For a single \"%%\" use \"%%%%\".")));
				break;
		}
	}

	/* 不再需要 deconstruct_array 的结果。 */
	if (fc_elements != NULL)
		pfree(fc_elements);
	if (fc_nulls != NULL)
		pfree(fc_nulls);

	/* 生成结果。 */
	fc_result = cstring_to_text_with_len(fc_str.data, fc_str.len);
	pfree(fc_str.data);

	PG_RETURN_TEXT_P(fc_result);
}

/*
 * 将连续数字解析为十进制数。
 *
 * 如果一些数字可以被解析，则返回 true。
 * 值返回到 *value 中，*ptr 被推进到下一个
 * 要解析的字符。
 *
 * 注意解析不变量：在进入时已知在字符串结束（end_ptr）
 * 之前至少有一个字符可用，并且在退出时仍然如此。
 */
static bool fc_text_format_parse_digits(const char **fc_ptr, const char *fc_end_ptr, int *fc_value)
{
	bool		fc_found = false;
	const char *fc_cp = *fc_ptr;
	int			fc_val = 0;

	while (*fc_cp >= '0' && *fc_cp <= '9')
	{
		int8		fc_digit = (*fc_cp - '0');

		if (unlikely(pg_mul_s32_overflow(fc_val, 10, &fc_val)) ||
			unlikely(pg_add_s32_overflow(fc_val, fc_digit, &fc_val)))
			ereport(ERROR,
					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
					 errmsg("number is out of range")));
		ADVANCE_PARSE_POINTER(fc_cp, fc_end_ptr);
		fc_found = true;
	}

	*fc_ptr = fc_cp;
	*fc_value = fc_val;

	return fc_found;
}

/*
 * 解析格式说明符（通常遵循 SUS printf 规范）。
 *
 * 我们已经在初始 '%' 之后推进，并且我们正在寻找
 * [argpos][flags][width]type（但类型字符在这里不被消费）。
 *
 * 输入是 start_ptr（'%' 之后的位置）和 end_ptr（字符串末尾 + 1）。
 * 输出参数：
 *	argpos：要打印的值的参数位置。 -1 意味着未指定。
 *	widthpos：宽度的参数位置。零意味着参数位置
 *			未指定（即，取下一个参数），-1 意味着没有宽度
 *			参数（宽度被省略或指定为常量）。
 *	flags：标志的位掩码。
 *	width：直接指定的宽度值。零意味着宽度被省略
 *			（请注意，将此情况与显式零宽度值区分开并不是必需的）。
 *
 * 函数结果是下一个要解析的字符位置，即
 * 类型字符的位置。
 *
 * 注意解析不变量：在进入时已知在字符串结束（end_ptr）
 * 之前至少有一个字符可用，并且在退出时仍然如此。
 */
static const char * fc_text_format_parse_format(const char *fc_start_ptr, const char *fc_end_ptr,
						 int *fc_argpos, int *fc_widthpos,
						 int *fc_flags, int *fc_width)
{
	const char *fc_cp = fc_start_ptr;
	int			fc_n;

	/* 为输出参数设置默认值 */
	*fc_argpos = -1;
	*fc_widthpos = -1;
	*fc_flags = 0;
	*fc_width = 0;

	/* 尝试识别第一个数字 */
	if (fc_text_format_parse_digits(&fc_cp, fc_end_ptr, &fc_n))
	{
		if (*fc_cp != '$')
		{
			/* 必须只有宽度和类型，所以我们完成了 */
			*fc_width = fc_n;
			return fc_cp;
		}
		/* 该数字是参数位置 */
		*fc_argpos = fc_n;
		/* 显式0对于参数索引被立即拒绝 */
		if (fc_n == 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("format specifies argument 0, but arguments are numbered from 1")));
		ADVANCE_PARSE_POINTER(fc_cp, fc_end_ptr);
	}

	/* 处理标志（目前只支持负号） */
	while (*fc_cp == '-')
	{
		*fc_flags |= TEXT_FORMAT_FLAG_MINUS;
		ADVANCE_PARSE_POINTER(fc_cp, fc_end_ptr);
	}

	if (*fc_cp == '*')
	{
		/* 处理间接宽度 */
		ADVANCE_PARSE_POINTER(fc_cp, fc_end_ptr);
		if (fc_text_format_parse_digits(&fc_cp, fc_end_ptr, &fc_n))
		{
			/* 此位置的数字必须以$结束 */
			if (*fc_cp != '$')
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("width argument position must be ended by \"$\"")));
			/* 这个数字是宽度参数位置 */
			*fc_widthpos = fc_n;
			/* 显式0对于参数索引被立即拒绝 */
			if (fc_n == 0)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("format specifies argument 0, but arguments are numbered from 1")));
			ADVANCE_PARSE_POINTER(fc_cp, fc_end_ptr);
		}
		else
			*fc_widthpos = 0;		/* 宽度的参数位置未指定 */
	}
	else
	{
		/* 检查直接宽度规范 */
		if (fc_text_format_parse_digits(&fc_cp, fc_end_ptr, &fc_n))
			*fc_width = fc_n;
	}

	/* cp 现在应该指向类型字符 */
	return fc_cp;
}

/*
 * 格式化 %s, %I 或 %L 转换
 */
static void fc_text_format_string_conversion(StringInfo fc_buf, char fc_conversion,
							  FmgrInfo *fc_typOutputInfo,
							  Datum fc_value, bool fc_isNull,
							  int fc_flags, int fc_width)
{
	char	   *fc_str;

	/* 在尝试将值字符串化之前处理 NULL 参数。 */
	if (fc_isNull)
	{
		if (fc_conversion == 's')
			fc_text_format_append_string(fc_buf, "", fc_flags, fc_width);
		else if (fc_conversion == 'L')
			fc_text_format_append_string(fc_buf, "NULL", fc_flags, fc_width);
		else if (fc_conversion == 'I')
			ereport(ERROR,
					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
					 errmsg("null values cannot be formatted as an SQL identifier")));
		return;
	}

	/* 字符串化。 */
	fc_str = OutputFunctionCall(fc_typOutputInfo, fc_value);

	/* Escape. */
	if (fc_conversion == 'I')
	{
		/* quote_identifier 可能会或者可能不会分配一个新字符串。 */
		fc_text_format_append_string(fc_buf, quote_identifier(fc_str), fc_flags, fc_width);
	}
	else if (fc_conversion == 'L')
	{
		char	   *fc_qstr = quote_literal_cstr(fc_str);

		fc_text_format_append_string(fc_buf, fc_qstr, fc_flags, fc_width);
		/* quote_literal_cstr() 始终分配一个新字符串 */
		pfree(fc_qstr);
	}
	else
		fc_text_format_append_string(fc_buf, fc_str, fc_flags, fc_width);

	/* 清理。 */
	pfree(fc_str);
}

/*
 * 根据标志/宽度要求将 str 附加到 buf，上述要求较强
 */
static void fc_text_format_append_string(StringInfo fc_buf, const char *fc_str,
						  int fc_flags, int fc_width)
{
	bool		fc_align_to_left = false;
	int			fc_len;

	/* 典型的简单情况的快速路径 */
	if (fc_width == 0)
	{
		appendStringInfoString(fc_buf, fc_str);
		return;
	}

	if (fc_width < 0)
	{
		/* 负宽度：隐含的 '-' 标志，然后取绝对值 */
		fc_align_to_left = true;
		/* -INT_MIN 是未定义的 */
		if (fc_width <= INT_MIN)
#ifdef FDD//cppcheck
		{
#endif
			ereport(ERROR,
					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
					 errmsg("number is out of range")));
#ifdef FDD//cppcheck
			return;
		}
#endif
		fc_width = -fc_width;
	}
	else if (fc_flags & TEXT_FORMAT_FLAG_MINUS)
		fc_align_to_left = true;

	fc_len = pg_mbstrlen(fc_str);
	if (fc_align_to_left)
	{
		/* 左对齐 */
		appendStringInfoString(fc_buf, fc_str);
		if (fc_len < fc_width)
			appendStringInfoSpaces(fc_buf, fc_width - fc_len);
	}
	else
	{
		/* 右对齐 */
		if (fc_len < fc_width)
			appendStringInfoSpaces(fc_buf, fc_width - fc_len);
		appendStringInfoString(fc_buf, fc_str);
	}
}

/*
 * text_format_nv - text_format 函数的非变参包装。
 *
 * 注意：此包装对于通过 opr_sanity 中的完整性检查是必要的，
 * 它检查所有共享实现 C 函数的内置函数是否具有相同数量的参数。
 */
Datum text_format_nv(PG_FUNCTION_ARGS)
{
	return text_format(fcinfo);
}

/*
 * Levenshtein 距离函数的辅助函数。比 memcmp() 更快，
 * 适用于这种用例。
 */
static inline bool rest_of_char_same(const char *s1, const char *s2, int len)
{
	while (len > 0)
	{
		len--;
		if (s1[len] != s2[len])
			return false;
	}
	return true;
}

/* 扩展每个 Levenshtein 距离变体 */
#include "levenshtein.c"
#define LEVENSHTEIN_LESS_EQUAL
#include "levenshtein.c"


/*
 * Unicode支持
 */

static UnicodeNormalizationForm
fc_unicode_norm_form_from_string(const char *fc_formstr)
{
	UnicodeNormalizationForm fc_form = -1;

	/*
	 * 不妨在这里检查一下。
	 */
	if (GetDatabaseEncoding() != PG_UTF8)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("Unicode normalization can only be performed if server encoding is UTF8")));

	if (pg_strcasecmp(fc_formstr, "NFC") == 0)
		fc_form = UNICODE_NFC;
	else if (pg_strcasecmp(fc_formstr, "NFD") == 0)
		fc_form = UNICODE_NFD;
	else if (pg_strcasecmp(fc_formstr, "NFKC") == 0)
		fc_form = UNICODE_NFKC;
	else if (pg_strcasecmp(fc_formstr, "NFKD") == 0)
		fc_form = UNICODE_NFKD;
	else
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid normalization form: %s", fc_formstr)));

	return fc_form;
}

Datum unicode_normalize_func(PG_FUNCTION_ARGS)
{
	text	   *fc_input = PG_GETARG_TEXT_PP(0);
	char	   *fc_formstr = text_to_cstring(PG_GETARG_TEXT_PP(1));
	UnicodeNormalizationForm fc_form;
	int			fc_size;
	pg_wchar   *fc_input_chars;
	pg_wchar   *fc_output_chars;
	unsigned char *fc_p;
	text	   *fc_result;
	int			fc_i;

	fc_form = fc_unicode_norm_form_from_string(fc_formstr);

	/* 转换为 pg_wchar */
	fc_size = pg_mbstrlen_with_len(VARDATA_ANY(fc_input), VARSIZE_ANY_EXHDR(fc_input));
	fc_input_chars = palloc((fc_size + 1) * sizeof(pg_wchar));
	fc_p = (unsigned char *) VARDATA_ANY(fc_input);
	for (fc_i = 0; fc_i < fc_size; fc_i++)
	{
		fc_input_chars[fc_i] = utf8_to_unicode(fc_p);
		fc_p += pg_utf_mblen(fc_p);
	}
	fc_input_chars[fc_i] = (pg_wchar) '\0';
	Assert((char *) fc_p == VARDATA_ANY(fc_input) + VARSIZE_ANY_EXHDR(fc_input));

	/* 操作 */
	fc_output_chars = unicode_normalize(fc_form, fc_input_chars);

	/* 转换回 UTF-8 字符串 */
	fc_size = 0;
	for (pg_wchar *fc_wp = fc_output_chars; *fc_wp; fc_wp++)
	{
		unsigned char fc_buf[4];

		unicode_to_utf8(*fc_wp, fc_buf);
		fc_size += pg_utf_mblen(fc_buf);
	}

	fc_result = palloc(fc_size + VARHDRSZ);
	SET_VARSIZE(fc_result, fc_size + VARHDRSZ);

	fc_p = (unsigned char *) VARDATA_ANY(fc_result);
	for (pg_wchar *fc_wp = fc_output_chars; *fc_wp; fc_wp++)
	{
		unicode_to_utf8(*fc_wp, fc_p);
		fc_p += pg_utf_mblen(fc_p);
	}
	Assert((char *) fc_p == (char *) fc_result + fc_size + VARHDRSZ);

	PG_RETURN_TEXT_P(fc_result);
}

/*
 * 检查字符串是否符合指定的 Unicode 标准化形式。
 *
 * 这是通过将字符串转换为指定的标准形式，然后与原始字符串进行比较来完成的。为了加快速度，我们还应用了 UAX 第 15 号中规定的“快速检查”算法，该算法可以通过一次扫描字符串为许多字符串提供是或否的答案。
 *
 * 此函数通常应该针对字符串实际上是标准化的情况进行优化。在这种情况下，我们将最终查看整个字符串，因此进行任何增量转换等可能没有价值。
 */
Datum unicode_is_normalized(PG_FUNCTION_ARGS)
{
	text	   *fc_input = PG_GETARG_TEXT_PP(0);
	char	   *fc_formstr = text_to_cstring(PG_GETARG_TEXT_PP(1));
	UnicodeNormalizationForm fc_form;
	int			fc_size;
	pg_wchar   *fc_input_chars;
	pg_wchar   *fc_output_chars;
	unsigned char *fc_p;
	int			fc_i;
	UnicodeNormalizationQC fc_quickcheck;
	int			fc_output_size;
	bool		fc_result;

	fc_form = fc_unicode_norm_form_from_string(fc_formstr);

	/* 转换为 pg_wchar */
	fc_size = pg_mbstrlen_with_len(VARDATA_ANY(fc_input), VARSIZE_ANY_EXHDR(fc_input));
	fc_input_chars = palloc((fc_size + 1) * sizeof(pg_wchar));
	fc_p = (unsigned char *) VARDATA_ANY(fc_input);
	for (fc_i = 0; fc_i < fc_size; fc_i++)
	{
		fc_input_chars[fc_i] = utf8_to_unicode(fc_p);
		fc_p += pg_utf_mblen(fc_p);
	}
	fc_input_chars[fc_i] = (pg_wchar) '\0';
	Assert((char *) fc_p == VARDATA_ANY(fc_input) + VARSIZE_ANY_EXHDR(fc_input));

	/* 快速检查（见 UAX 第 15 号） */
	fc_quickcheck = unicode_is_normalized_quickcheck(fc_form, fc_input_chars);
	if (fc_quickcheck == UNICODE_NORM_QC_YES)
		PG_RETURN_BOOL(true);
	else if (fc_quickcheck == UNICODE_NORM_QC_NO)
		PG_RETURN_BOOL(false);

	/* 标准化并与原始进行比较 */
	fc_output_chars = unicode_normalize(fc_form, fc_input_chars);

	fc_output_size = 0;
	for (pg_wchar *fc_wp = fc_output_chars; *fc_wp; fc_wp++)
		fc_output_size++;

	fc_result = (fc_size == fc_output_size) &&
		(memcmp(fc_input_chars, fc_output_chars, fc_size * sizeof(pg_wchar)) == 0);

	PG_RETURN_BOOL(fc_result);
}

/*
 * 检查前 n 个字符是否是十六进制数字
 */
static bool fc_isxdigits_n(const char *fc_instr, size_t fc_n)
{
	for (size_t fc_i = 0; fc_i < fc_n; fc_i++)
		if (!isxdigit((unsigned char) fc_instr[fc_i]))
			return false;

	return true;
}

static unsigned int fc_hexval(unsigned char fc_c)
{
	if (fc_c >= '0' && fc_c <= '9')
		return fc_c - '0';
	if (fc_c >= 'a' && fc_c <= 'f')
		return fc_c - 'a' + 0xA;
	if (fc_c >= 'A' && fc_c <= 'F')
		return fc_c - 'A' + 0xA;
	elog(ERROR, "invalid hexadecimal digit");
	return 0;					/* 未到达 */
}

/*
 * 将十六进制数字字符串转换为数字
 */
static unsigned int fc_hexval_n(const char *fc_instr, size_t fc_n)
{
	unsigned int fc_result = 0;

	for (size_t fc_i = 0; fc_i < fc_n; fc_i++)
		fc_result += fc_hexval(fc_instr[fc_i]) << (4 * (fc_n - fc_i - 1));

	return fc_result;
}

/*
 * 用 Unicode 字符替换 Unicode 转义序列
 */
Datum unistr(PG_FUNCTION_ARGS)
{
	text	   *fc_input_text = PG_GETARG_TEXT_PP(0);
	char	   *fc_instr;
	int			fc_len;
	StringInfoData fc_str;
	text	   *fc_result;
	pg_wchar	fc_pair_first = 0;
	char		fc_cbuf[MAX_UNICODE_EQUIVALENT_STRING + 1];

	fc_instr = VARDATA_ANY(fc_input_text);
	fc_len = VARSIZE_ANY_EXHDR(fc_input_text);

	initStringInfo(&fc_str);

	while (fc_len > 0)
	{
		if (fc_instr[0] == '\\')
		{
			if (fc_len >= 2 &&
				fc_instr[1] == '\\')
			{
				if (fc_pair_first)
					goto invalid_pair;
				appendStringInfoChar(&fc_str, '\\');
				fc_instr += 2;
				fc_len -= 2;
			}
			else if ((fc_len >= 5 && fc_isxdigits_n(fc_instr + 1, 4)) ||
					 (fc_len >= 6 && fc_instr[1] == 'u' && fc_isxdigits_n(fc_instr + 2, 4)))
			{
				pg_wchar	fc_unicode;
				int			fc_offset = fc_instr[1] == 'u' ? 2 : 1;

				fc_unicode = fc_hexval_n(fc_instr + fc_offset, 4);

				if (!is_valid_unicode_codepoint(fc_unicode))
					ereport(ERROR,
							errcode(ERRCODE_INVALID_PARAMETER_VALUE),
							errmsg("invalid Unicode code point: %04X", fc_unicode));

				if (fc_pair_first)
				{
					if (is_utf16_surrogate_second(fc_unicode))
					{
						fc_unicode = surrogate_pair_to_codepoint(fc_pair_first, fc_unicode);
						fc_pair_first = 0;
					}
					else
						goto invalid_pair;
				}
				else if (is_utf16_surrogate_second(fc_unicode))
					goto invalid_pair;

				if (is_utf16_surrogate_first(fc_unicode))
					fc_pair_first = fc_unicode;
				else
				{
					pg_unicode_to_server(fc_unicode, (unsigned char *) fc_cbuf);
					appendStringInfoString(&fc_str, fc_cbuf);
				}

				fc_instr += 4 + fc_offset;
				fc_len -= 4 + fc_offset;
			}
			else if (fc_len >= 8 && fc_instr[1] == '+' && fc_isxdigits_n(fc_instr + 2, 6))
			{
				pg_wchar	fc_unicode;

				fc_unicode = fc_hexval_n(fc_instr + 2, 6);

				if (!is_valid_unicode_codepoint(fc_unicode))
					ereport(ERROR,
							errcode(ERRCODE_INVALID_PARAMETER_VALUE),
							errmsg("invalid Unicode code point: %04X", fc_unicode));

				if (fc_pair_first)
				{
					if (is_utf16_surrogate_second(fc_unicode))
					{
						fc_unicode = surrogate_pair_to_codepoint(fc_pair_first, fc_unicode);
						fc_pair_first = 0;
					}
					else
						goto invalid_pair;
				}
				else if (is_utf16_surrogate_second(fc_unicode))
					goto invalid_pair;

				if (is_utf16_surrogate_first(fc_unicode))
					fc_pair_first = fc_unicode;
				else
				{
					pg_unicode_to_server(fc_unicode, (unsigned char *) fc_cbuf);
					appendStringInfoString(&fc_str, fc_cbuf);
				}

				fc_instr += 8;
				fc_len -= 8;
			}
			else if (fc_len >= 10 && fc_instr[1] == 'U' && fc_isxdigits_n(fc_instr + 2, 8))
			{
				pg_wchar	fc_unicode;

				fc_unicode = fc_hexval_n(fc_instr + 2, 8);

				if (!is_valid_unicode_codepoint(fc_unicode))
					ereport(ERROR,
							errcode(ERRCODE_INVALID_PARAMETER_VALUE),
							errmsg("invalid Unicode code point: %04X", fc_unicode));

				if (fc_pair_first)
				{
					if (is_utf16_surrogate_second(fc_unicode))
					{
						fc_unicode = surrogate_pair_to_codepoint(fc_pair_first, fc_unicode);
						fc_pair_first = 0;
					}
					else
						goto invalid_pair;
				}
				else if (is_utf16_surrogate_second(fc_unicode))
					goto invalid_pair;

				if (is_utf16_surrogate_first(fc_unicode))
					fc_pair_first = fc_unicode;
				else
				{
					pg_unicode_to_server(fc_unicode, (unsigned char *) fc_cbuf);
					appendStringInfoString(&fc_str, fc_cbuf);
				}

				fc_instr += 10;
				fc_len -= 10;
			}
			else
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("invalid Unicode escape"),
						 errhint("Unicode escapes must be \\XXXX, \\+XXXXXX, \\uXXXX, or \\UXXXXXXXX.")));
		}
		else
		{
			if (fc_pair_first)
				goto invalid_pair;

			appendStringInfoChar(&fc_str, *fc_instr++);
			fc_len--;
		}
	}

	/* 未完成的代理对？ */
	if (fc_pair_first)
		goto invalid_pair;

	fc_result = cstring_to_text_with_len(fc_str.data, fc_str.len);
	pfree(fc_str.data);

	PG_RETURN_TEXT_P(fc_result);

invalid_pair:
	ereport(ERROR,
			(errcode(ERRCODE_SYNTAX_ERROR),
			 errmsg("invalid Unicode surrogate pair")));
	PG_RETURN_NULL();			/* 保持编译器安静 */
}
